diff --git a/.env.example.complete b/.env.example.complete index 96a3b448f..0853bd1fe 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -72,7 +72,7 @@ MYSQL_ATTR_SSL_CA="/path/to/ca.pem" # Mail configuration # Refer to https://www.bookstackapp.com/docs/admin/email-webhooks/#email-configuration MAIL_DRIVER=smtp -MAIL_FROM=mail@bookstackapp.com +MAIL_FROM=bookstack@example.com MAIL_FROM_NAME=BookStack MAIL_HOST=localhost @@ -359,6 +359,15 @@ ALLOWED_IFRAME_HOSTS=null # Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured. ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com" +# A list of the sources/hostnames that can be reached by application SSR calls. +# This is used wherever users can provide URLs/hosts in-platform, like for webhooks. +# Host-specific functionality (usually controlled via other options) like auth +# or user avatars for example, won't use this list. +# Space seperated if multiple. Can use '*' as a wildcard. +# Values will be compared prefix-matched, case-insensitive, against called SSR urls. +# Defaults to allow all hosts. +ALLOWED_SSR_HOSTS="*" + # The default and maximum item-counts for listing API requests. API_DEFAULT_ITEM_COUNT=100 API_MAX_ITEM_COUNT=500 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5c8734fd6..d301826c9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,14 @@ name: Bug Report -description: Create a report to help us improve or fix things +description: Create a report to help us fix bugs & issues in existing supported functionality labels: [":bug: Bug"] body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out a bug report! + Please note that this form is for reporting bugs in existing supported functionality. + + If you are reporting something that's not an issue in functionality we've previously supported and/or is simply something different to your expectations, then it may be more appropriate to raise via a feature or support request instead. - type: textarea id: description attributes: @@ -13,7 +20,7 @@ body: id: reproduction attributes: label: Steps to Reproduce - description: Detail the steps that would replicate this issue + description: Detail the steps that would replicate this issue. placeholder: | 1. Go to '...' 2. Click on '....' @@ -32,7 +39,7 @@ body: id: context attributes: label: Screenshots or Additional Context - description: Provide any additional context and screenshots here to help us solve this issue + description: Provide any additional context and screenshots here to help us solve this issue. validations: required: false - type: input @@ -48,23 +55,7 @@ body: id: bsversion attributes: label: Exact BookStack Version - description: This can be found in the settings view of BookStack. Please provide an exact version. - placeholder: (eg. v21.08.5) - validations: - required: true - - type: input - id: phpversion - attributes: - label: PHP Version - description: Keep in mind your command-line PHP version may differ to that of your webserver. Provide that relevant to the issue. - placeholder: (eg. 7.4) - validations: - required: false - - type: textarea - id: hosting - attributes: - label: Hosting Environment - description: Describe your hosting environment as much as possible including any proxies used (If applicable). - placeholder: (eg. Ubuntu 20.04 VPS, installed using official installation script) + description: This can be found in the settings view of BookStack. Please provide an exact version(s) you've tested on. + placeholder: (eg. v23.06.7) validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index fb1e0b3b0..0ebb8e72f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -33,9 +33,9 @@ body: attributes: label: Have you searched for an existing open/closed issue? description: | - To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundemental benefit/goal of your request. + To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundamental benefit/goal of your request. options: - - label: I have searched for existing issues and none cover my fundemental request + - label: I have searched for existing issues and none cover my fundamental request required: true - type: dropdown id: existing_usage @@ -43,8 +43,8 @@ body: label: How long have you been using BookStack? options: - Not using yet, just scoping - - 0 to 6 months - - 6 months to 1 year + - Under 3 months + - 3 months to 1 year - 1 to 5 years - Over 5 years validations: diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.github/ISSUE_TEMPLATE/support_request.yml index cb2476546..4db8d1ced 100644 --- a/.github/ISSUE_TEMPLATE/support_request.yml +++ b/.github/ISSUE_TEMPLATE/support_request.yml @@ -33,7 +33,7 @@ body: attributes: label: Exact BookStack Version description: This can be found in the settings view of BookStack. Please provide an exact version. - placeholder: (eg. v21.08.5) + placeholder: (eg. v23.06.7) validations: required: true - type: textarea @@ -44,19 +44,11 @@ body: placeholder: Be sure to remove any confidential details in your logs validations: required: false - - type: input - id: phpversion - attributes: - label: PHP Version - description: Keep in mind your command-line PHP version may differ to that of your webserver. Provide that most relevant to the issue. - placeholder: (eg. 7.4) - validations: - required: false - type: textarea id: hosting attributes: label: Hosting Environment description: Describe your hosting environment as much as possible including any proxies used (If applicable). - placeholder: (eg. Ubuntu 20.04 VPS, installed using official installation script) + placeholder: (eg. PHP8.1 on Ubuntu 22.04 VPS, installed using official installation script) validations: required: true diff --git a/.github/translators.txt b/.github/translators.txt index 74279263f..e07730183 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -57,6 +57,7 @@ Name :: Languages @Jokuna :: Korean @smartshogu :: German; German Informal @samadha56 :: Persian +@mrmuminov :: Uzbek cipi1965 :: Italian Mykola Ronik (Mantikor) :: Ukrainian furkanoyk :: Turkish @@ -289,7 +290,7 @@ Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian LiZerui (CNLiZerui) :: Chinese Traditional Fabrice Boyer (FabriceBoyer) :: French mikael (bitcanon) :: Swedish -Matthias Mai (schnapsidee) :: German; German Informal +Matthias Mai (schnapsidee) :: German Informal; German Ufuk Ayyıldız (ufukayyildiz) :: Turkish Jan Mitrof (jan.kachlik) :: Czech edwardsmirnov :: Russian @@ -344,3 +345,18 @@ hamidreza amini (hamidrezaamini2022) :: Persian Tomislav Kraljević (tomislav.kraljevic) :: Croatian Taygun Yıldırım (yildirimtaygun) :: Turkish robing29 :: German +Bruno Eduardo de Jesus Barroso (brunoejb) :: Portuguese, Brazilian +Igor V Belousov (biv) :: Russian +David Bauer (davbauer) :: German +Guttorm Hveem (guttormhveem) :: Norwegian Bokmal; Norwegian Nynorsk +Minh Giang Truong (minhgiang1204) :: Vietnamese +Ioannis Ioannides (i.ioannides) :: Greek +Vadim (vadrozh) :: Russian +Flip333 :: German Informal; German +Paulo Henrique (paulohsantos114) :: Portuguese, Brazilian +Dženan (Dzenan) :: Swedish +Péter Péli (peter.peli) :: Hungarian +TWME :: Chinese Traditional +Sascha (Man-in-Black) :: German +Mohammadreza Madadi (madadi.efl) :: Persian +Konstantin Kovacheli (kkovacheli) :: Ukrainian diff --git a/app/Access/Controllers/ThrottlesLogins.php b/app/Access/Controllers/ThrottlesLogins.php index 2a066dab5..25c3452f2 100644 --- a/app/Access/Controllers/ThrottlesLogins.php +++ b/app/Access/Controllers/ThrottlesLogins.php @@ -71,7 +71,7 @@ trait ThrottlesLogins */ protected function limiter(): RateLimiter { - return app(RateLimiter::class); + return app()->make(RateLimiter::class); } /** diff --git a/app/Access/EmailConfirmationService.php b/app/Access/EmailConfirmationService.php index 79220f996..de9ea69b8 100644 --- a/app/Access/EmailConfirmationService.php +++ b/app/Access/EmailConfirmationService.php @@ -2,8 +2,8 @@ namespace BookStack\Access; +use BookStack\Access\Notifications\ConfirmEmailNotification; use BookStack\Exceptions\ConfirmationEmailException; -use BookStack\Notifications\ConfirmEmail; use BookStack\Users\Models\User; class EmailConfirmationService extends UserTokenService @@ -26,7 +26,7 @@ class EmailConfirmationService extends UserTokenService $this->deleteByUser($user); $token = $this->createTokenForUser($user); - $user->notify(new ConfirmEmail($token)); + $user->notify(new ConfirmEmailNotification($token)); } /** diff --git a/app/Access/Notifications/ConfirmEmailNotification.php b/app/Access/Notifications/ConfirmEmailNotification.php new file mode 100644 index 000000000..c67e1cf62 --- /dev/null +++ b/app/Access/Notifications/ConfirmEmailNotification.php @@ -0,0 +1,26 @@ + setting('app-name')]; + + return $this->newMailMessage() + ->subject(trans('auth.email_confirm_subject', $appName)) + ->greeting(trans('auth.email_confirm_greeting', $appName)) + ->line(trans('auth.email_confirm_text')) + ->action(trans('auth.email_confirm_action'), url('/register/confirm/' . $this->token)); + } +} diff --git a/app/Access/Notifications/ResetPasswordNotification.php b/app/Access/Notifications/ResetPasswordNotification.php new file mode 100644 index 000000000..0a0f4ceb7 --- /dev/null +++ b/app/Access/Notifications/ResetPasswordNotification.php @@ -0,0 +1,24 @@ +newMailMessage() + ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')])) + ->line(trans('auth.email_reset_text')) + ->action(trans('auth.reset_password'), url('password/reset/' . $this->token)) + ->line(trans('auth.email_reset_not_requested')); + } +} diff --git a/app/Access/Notifications/UserInviteNotification.php b/app/Access/Notifications/UserInviteNotification.php new file mode 100644 index 000000000..dacce574f --- /dev/null +++ b/app/Access/Notifications/UserInviteNotification.php @@ -0,0 +1,27 @@ + setting('app-name')]; + $locale = $notifiable->getLocale(); + + return $this->newMailMessage($locale) + ->subject($locale->trans('auth.user_invite_email_subject', $appName)) + ->greeting($locale->trans('auth.user_invite_email_greeting', $appName)) + ->line($locale->trans('auth.user_invite_email_text')) + ->action($locale->trans('auth.user_invite_email_action'), url('/register/invite/' . $this->token)); + } +} diff --git a/app/Access/Oidc/OidcOAuthProvider.php b/app/Access/Oidc/OidcOAuthProvider.php index 2ed8cd5c9..d2dc829b7 100644 --- a/app/Access/Oidc/OidcOAuthProvider.php +++ b/app/Access/Oidc/OidcOAuthProvider.php @@ -20,15 +20,8 @@ class OidcOAuthProvider extends AbstractProvider { use BearerAuthorizationTrait; - /** - * @var string - */ - protected $authorizationEndpoint; - - /** - * @var string - */ - protected $tokenEndpoint; + protected string $authorizationEndpoint; + protected string $tokenEndpoint; /** * Scopes to use for the OIDC authorization call. @@ -60,7 +53,7 @@ class OidcOAuthProvider extends AbstractProvider } /** - * Add an additional scope to this provider upon the default. + * Add another scope to this provider upon the default. */ public function addScope(string $scope): void { diff --git a/app/Access/Oidc/OidcProviderSettings.php b/app/Access/Oidc/OidcProviderSettings.php index 9c8b1b264..fa3f579b1 100644 --- a/app/Access/Oidc/OidcProviderSettings.php +++ b/app/Access/Oidc/OidcProviderSettings.php @@ -59,7 +59,7 @@ class OidcProviderSettings } } - if (strpos($this->issuer, 'https://') !== 0) { + if (!str_starts_with($this->issuer, 'https://')) { throw new InvalidArgumentException('Issuer value must start with https://'); } } diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php index 6d13fe8f1..8778cbd98 100644 --- a/app/Access/Oidc/OidcService.php +++ b/app/Access/Oidc/OidcService.php @@ -9,13 +9,13 @@ use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Exceptions\UserRegistrationException; use BookStack\Facades\Theme; +use BookStack\Http\HttpRequestService; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; -use Psr\Http\Client\ClientInterface as HttpClient; /** * Class OpenIdConnectService @@ -26,7 +26,7 @@ class OidcService public function __construct( protected RegistrationService $registrationService, protected LoginService $loginService, - protected HttpClient $httpClient, + protected HttpRequestService $http, protected GroupSyncService $groupService ) { } @@ -94,7 +94,7 @@ class OidcService // Run discovery if ($config['discover'] ?? false) { try { - $settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15); + $settings->discoverFromIssuer($this->http->buildClient(5), Cache::store(null), 15); } catch (OidcIssuerDiscoveryException $exception) { throw new OidcException('OIDC Discovery Error: ' . $exception->getMessage()); } @@ -111,7 +111,7 @@ class OidcService protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider { $provider = new OidcOAuthProvider($settings->arrayForProvider(), [ - 'httpClient' => $this->httpClient, + 'httpClient' => $this->http->buildClient(5), 'optionProvider' => new HttpBasicAuthOptionProvider(), ]); @@ -142,10 +142,11 @@ class OidcService */ protected function getUserDisplayName(OidcIdToken $token, string $defaultValue): string { - $displayNameAttr = $this->config()['display_name_claims']; + $displayNameAttrString = $this->config()['display_name_claims'] ?? ''; + $displayNameAttrs = explode('|', $displayNameAttrString); $displayName = []; - foreach ($displayNameAttr as $dnAttr) { + foreach ($displayNameAttrs as $dnAttr) { $dnComponent = $token->getClaim($dnAttr) ?? ''; if ($dnComponent !== '') { $displayName[] = $dnComponent; diff --git a/app/Access/UserInviteService.php b/app/Access/UserInviteService.php index 00e361e82..7d955f385 100644 --- a/app/Access/UserInviteService.php +++ b/app/Access/UserInviteService.php @@ -2,7 +2,7 @@ namespace BookStack\Access; -use BookStack\Notifications\UserInvite; +use BookStack\Access\Notifications\UserInviteNotification; use BookStack\Users\Models\User; class UserInviteService extends UserTokenService @@ -18,6 +18,6 @@ class UserInviteService extends UserTokenService { $this->deleteByUser($user); $token = $this->createTokenForUser($user); - $user->notify(new UserInvite($token)); + $user->notify(new UserInviteNotification($token)); } } diff --git a/app/Activity/Controllers/FavouriteController.php b/app/Activity/Controllers/FavouriteController.php index 1b88ffd64..d2534ddfe 100644 --- a/app/Activity/Controllers/FavouriteController.php +++ b/app/Activity/Controllers/FavouriteController.php @@ -6,11 +6,17 @@ use BookStack\Activity\Models\Favouritable; use BookStack\App\Model; use BookStack\Entities\Models\Entity; use BookStack\Entities\Queries\TopFavourites; +use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Http\Controller; use Illuminate\Http\Request; class FavouriteController extends Controller { + public function __construct( + protected MixedEntityRequestHelper $entityHelper, + ) { + } + /** * Show a listing of all favourite items for the current user. */ @@ -36,13 +42,14 @@ class FavouriteController extends Controller */ public function add(Request $request) { - $favouritable = $this->getValidatedModelFromRequest($request); - $favouritable->favourites()->firstOrCreate([ + $modelInfo = $this->validate($request, $this->entityHelper->validationRules()); + $entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo); + $entity->favourites()->firstOrCreate([ 'user_id' => user()->id, ]); $this->showSuccessNotification(trans('activities.favourite_add_notification', [ - 'name' => $favouritable->name, + 'name' => $entity->name, ])); return redirect()->back(); @@ -53,48 +60,16 @@ class FavouriteController extends Controller */ public function remove(Request $request) { - $favouritable = $this->getValidatedModelFromRequest($request); - $favouritable->favourites()->where([ + $modelInfo = $this->validate($request, $this->entityHelper->validationRules()); + $entity = $this->entityHelper->getVisibleEntityFromRequestData($modelInfo); + $entity->favourites()->where([ 'user_id' => user()->id, ])->delete(); $this->showSuccessNotification(trans('activities.favourite_remove_notification', [ - 'name' => $favouritable->name, + 'name' => $entity->name, ])); return redirect()->back(); } - - /** - * @throws \Illuminate\Validation\ValidationException - * @throws \Exception - */ - protected function getValidatedModelFromRequest(Request $request): Entity - { - $modelInfo = $this->validate($request, [ - 'type' => ['required', 'string'], - 'id' => ['required', 'integer'], - ]); - - if (!class_exists($modelInfo['type'])) { - throw new \Exception('Model not found'); - } - - /** @var Model $model */ - $model = new $modelInfo['type'](); - if (!$model instanceof Favouritable) { - throw new \Exception('Model not favouritable'); - } - - $modelInstance = $model->newQuery() - ->where('id', '=', $modelInfo['id']) - ->first(['id', 'name', 'owned_by']); - - $inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance)); - if (is_null($modelInstance) || $inaccessibleEntity) { - throw new \Exception('Model instance not found'); - } - - return $modelInstance; - } } diff --git a/app/Activity/Controllers/WatchController.php b/app/Activity/Controllers/WatchController.php new file mode 100644 index 000000000..c0b1c5872 --- /dev/null +++ b/app/Activity/Controllers/WatchController.php @@ -0,0 +1,29 @@ +checkPermission('receive-notifications'); + $this->preventGuestAccess(); + + $requestData = $this->validate($request, array_merge([ + 'level' => ['required', 'string'], + ], $entityHelper->validationRules())); + + $watchable = $entityHelper->getVisibleEntityFromRequestData($requestData); + $watchOptions = new UserEntityWatchOptions(user(), $watchable); + $watchOptions->updateLevelByName($requestData['level']); + + $this->showSuccessNotification(trans('activities.watch_update_level_notification')); + + return redirect()->back(); + } +} diff --git a/app/Activity/DispatchWebhookJob.php b/app/Activity/DispatchWebhookJob.php index f2330c4fa..e1771b114 100644 --- a/app/Activity/DispatchWebhookJob.php +++ b/app/Activity/DispatchWebhookJob.php @@ -6,14 +6,15 @@ use BookStack\Activity\Models\Loggable; use BookStack\Activity\Models\Webhook; use BookStack\Activity\Tools\WebhookFormatter; use BookStack\Facades\Theme; +use BookStack\Http\HttpRequestService; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; +use BookStack\Util\SsrUrlValidator; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class DispatchWebhookJob implements ShouldQueue @@ -48,23 +49,28 @@ class DispatchWebhookJob implements ShouldQueue * * @return void */ - public function handle() + public function handle(HttpRequestService $http) { $lastError = null; try { - $response = Http::asJson() - ->withOptions(['allow_redirects' => ['strict' => true]]) - ->timeout($this->webhook->timeout) - ->post($this->webhook->endpoint, $this->webhookData); - } catch (\Exception $exception) { - $lastError = $exception->getMessage(); - Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\""); - } + (new SsrUrlValidator())->ensureAllowed($this->webhook->endpoint); - if (isset($response) && $response->failed()) { - $lastError = "Response status from endpoint was {$response->status()}"; - Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with status {$response->status()}"); + $client = $http->buildClient($this->webhook->timeout, [ + 'connect_timeout' => 10, + 'allow_redirects' => ['strict' => true], + ]); + + $response = $client->sendRequest($http->jsonRequest('POST', $this->webhook->endpoint, $this->webhookData)); + $statusCode = $response->getStatusCode(); + + if ($statusCode >= 400) { + $lastError = "Response status from endpoint was {$statusCode}"; + Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with status {$statusCode}"); + } + } catch (\Exception $error) { + $lastError = $error->getMessage(); + Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\""); } $this->webhook->last_called_at = now(); diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 7aea6124a..6efa3df6f 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -5,16 +5,19 @@ namespace BookStack\Activity\Models; use BookStack\App\Model; use BookStack\Users\Models\HasCreatorAndUpdater; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; /** * @property int $id * @property string $text * @property string $html - * @property int|null $parent_id + * @property int|null $parent_id - Relates to local_id, not id * @property int $local_id * @property string $entity_type * @property int $entity_id + * @property int $created_by + * @property int $updated_by */ class Comment extends Model implements Loggable { @@ -32,6 +35,16 @@ class Comment extends Model implements Loggable return $this->morphTo('entity'); } + /** + * Get the parent comment this is in reply to (if existing). + */ + public function parent(): BelongsTo + { + return $this->belongsTo(Comment::class, 'parent_id', 'local_id', 'parent') + ->where('entity_type', '=', $this->entity_type) + ->where('entity_id', '=', $this->entity_id); + } + /** * Check if a comment has been updated since creation. */ @@ -42,20 +55,16 @@ class Comment extends Model implements Loggable /** * Get created date as a relative diff. - * - * @return mixed */ - public function getCreatedAttribute() + public function getCreatedAttribute(): string { return $this->created_at->diffForHumans(); } /** * Get updated date as a relative diff. - * - * @return mixed */ - public function getUpdatedAttribute() + public function getUpdatedAttribute(): string { return $this->updated_at->diffForHumans(); } diff --git a/app/Activity/Models/View.php b/app/Activity/Models/View.php index 512e08295..30ead1193 100644 --- a/app/Activity/Models/View.php +++ b/app/Activity/Models/View.php @@ -41,7 +41,7 @@ class View extends Model public static function incrementFor(Viewable $viewable): int { $user = user(); - if (is_null($user) || $user->isDefault()) { + if ($user->isGuest()) { return 0; } diff --git a/app/Activity/Models/Watch.php b/app/Activity/Models/Watch.php new file mode 100644 index 000000000..dfb72cc0a --- /dev/null +++ b/app/Activity/Models/Watch.php @@ -0,0 +1,45 @@ +morphTo(); + } + + public function jointPermissions(): HasMany + { + return $this->hasMany(JointPermission::class, 'entity_id', 'watchable_id') + ->whereColumn('watches.watchable_type', '=', 'joint_permissions.entity_type'); + } + + public function getLevelName(): string + { + return WatchLevels::levelValueToName($this->level); + } + + public function ignoring(): bool + { + return $this->level === WatchLevels::IGNORE; + } +} diff --git a/app/Activity/Notifications/Handlers/BaseNotificationHandler.php b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php new file mode 100644 index 000000000..b5f339b2c --- /dev/null +++ b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php @@ -0,0 +1,42 @@ + $notification + * @param int[] $userIds + */ + protected function sendNotificationToUserIds(string $notification, array $userIds, User $initiator, string|Loggable $detail, Entity $relatedModel): void + { + $users = User::query()->whereIn('id', array_unique($userIds))->get(); + + foreach ($users as $user) { + // Prevent sending to the user that initiated the activity + if ($user->id === $initiator->id) { + continue; + } + + // Prevent sending of the user does not have notification permissions + if (!$user->can('receive-notifications')) { + continue; + } + + // Prevent sending if the user does not have access to the related content + $permissions = new PermissionApplicator($user); + if (!$permissions->checkOwnableUserAccess($relatedModel, 'view')) { + continue; + } + + // Send the notification + $user->notify(new $notification($detail, $initiator)); + } + } +} diff --git a/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php b/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php new file mode 100644 index 000000000..bc12c8566 --- /dev/null +++ b/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php @@ -0,0 +1,48 @@ +entity; + $watchers = new EntityWatchers($page, WatchLevels::COMMENTS); + $watcherIds = $watchers->getWatcherUserIds(); + + // Page owner if user preferences allow + if (!$watchers->isUserIgnoring($page->owned_by) && $page->ownedBy) { + $userNotificationPrefs = new UserNotificationPreferences($page->ownedBy); + if ($userNotificationPrefs->notifyOnOwnPageComments()) { + $watcherIds[] = $page->owned_by; + } + } + + // Parent comment creator if preferences allow + $parentComment = $detail->parent()->first(); + if ($parentComment && !$watchers->isUserIgnoring($parentComment->created_by) && $parentComment->createdBy) { + $parentCommenterNotificationsPrefs = new UserNotificationPreferences($parentComment->createdBy); + if ($parentCommenterNotificationsPrefs->notifyOnCommentReplies()) { + $watcherIds[] = $parentComment->created_by; + } + } + + $this->sendNotificationToUserIds(CommentCreationNotification::class, $watcherIds, $user, $detail, $page); + } +} diff --git a/app/Activity/Notifications/Handlers/NotificationHandler.php b/app/Activity/Notifications/Handlers/NotificationHandler.php new file mode 100644 index 000000000..8c5498664 --- /dev/null +++ b/app/Activity/Notifications/Handlers/NotificationHandler.php @@ -0,0 +1,17 @@ +sendNotificationToUserIds(PageCreationNotification::class, $watchers->getWatcherUserIds(), $user, $detail, $detail); + } +} diff --git a/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php b/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php new file mode 100644 index 000000000..744aba18f --- /dev/null +++ b/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php @@ -0,0 +1,51 @@ +activity() + ->where('type', '=', ActivityType::PAGE_UPDATE) + ->where('id', '!=', $activity->id) + ->latest('created_at') + ->first(); + + // Return if the same user has already updated the page in the last 15 mins + if ($lastUpdate && $lastUpdate->user_id === $user->id) { + if ($lastUpdate->created_at->gt(now()->subMinutes(15))) { + return; + } + } + + // Get active watchers + $watchers = new EntityWatchers($detail, WatchLevels::UPDATES); + $watcherIds = $watchers->getWatcherUserIds(); + + // Add page owner if preferences allow + if (!$watchers->isUserIgnoring($detail->owned_by) && $detail->ownedBy) { + $userNotificationPrefs = new UserNotificationPreferences($detail->ownedBy); + if ($userNotificationPrefs->notifyOnOwnPageChanges()) { + $watcherIds[] = $detail->owned_by; + } + } + + $this->sendNotificationToUserIds(PageUpdateNotification::class, $watcherIds, $user, $detail, $detail); + } +} diff --git a/app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php b/app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php new file mode 100644 index 000000000..45ae82571 --- /dev/null +++ b/app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php @@ -0,0 +1,33 @@ +url) . '">' . e($this->linkText) . ''; + return str_replace(':link', $link, e($this->line)); + } + + public function __toString(): string + { + $link = "{$this->linkText} ({$this->url})"; + return str_replace(':link', $link, $this->line); + } +} diff --git a/app/Activity/Notifications/MessageParts/ListMessageLine.php b/app/Activity/Notifications/MessageParts/ListMessageLine.php new file mode 100644 index 000000000..9a729aa22 --- /dev/null +++ b/app/Activity/Notifications/MessageParts/ListMessageLine.php @@ -0,0 +1,36 @@ +list as $header => $content) { + $list[] = '' . e($header) . ' ' . e($content); + } + return implode("
\n", $list); + } + + public function __toString(): string + { + $list = []; + foreach ($this->list as $header => $content) { + $list[] = $header . ' ' . $content; + } + return implode("\n", $list); + } +} diff --git a/app/Activity/Notifications/Messages/BaseActivityNotification.php b/app/Activity/Notifications/Messages/BaseActivityNotification.php new file mode 100644 index 000000000..322df5d94 --- /dev/null +++ b/app/Activity/Notifications/Messages/BaseActivityNotification.php @@ -0,0 +1,47 @@ + $this->detail, + 'activity_creator' => $this->user, + ]; + } + + /** + * Build the common reason footer line used in mail messages. + */ + protected function buildReasonFooterLine(LocaleDefinition $locale): LinkedMailMessageLine + { + return new LinkedMailMessageLine( + url('/preferences/notifications'), + $locale->trans('notifications.footer_reason'), + $locale->trans('notifications.footer_reason_link'), + ); + } +} diff --git a/app/Activity/Notifications/Messages/CommentCreationNotification.php b/app/Activity/Notifications/Messages/CommentCreationNotification.php new file mode 100644 index 000000000..094ab30b7 --- /dev/null +++ b/app/Activity/Notifications/Messages/CommentCreationNotification.php @@ -0,0 +1,33 @@ +detail; + /** @var Page $page */ + $page = $comment->entity; + + $locale = $notifiable->getLocale(); + + return $this->newMailMessage($locale) + ->subject($locale->trans('notifications.new_comment_subject', ['pageName' => $page->getShortName()])) + ->line($locale->trans('notifications.new_comment_intro', ['appName' => setting('app-name')])) + ->line(new ListMessageLine([ + $locale->trans('notifications.detail_page_name') => $page->name, + $locale->trans('notifications.detail_commenter') => $this->user->name, + $locale->trans('notifications.detail_comment') => strip_tags($comment->html), + ])) + ->action($locale->trans('notifications.action_view_comment'), $page->getUrl('#comment' . $comment->local_id)) + ->line($this->buildReasonFooterLine($locale)); + } +} diff --git a/app/Activity/Notifications/Messages/PageCreationNotification.php b/app/Activity/Notifications/Messages/PageCreationNotification.php new file mode 100644 index 000000000..da028aa8c --- /dev/null +++ b/app/Activity/Notifications/Messages/PageCreationNotification.php @@ -0,0 +1,29 @@ +detail; + + $locale = $notifiable->getLocale(); + + return $this->newMailMessage($locale) + ->subject($locale->trans('notifications.new_page_subject', ['pageName' => $page->getShortName()])) + ->line($locale->trans('notifications.new_page_intro', ['appName' => setting('app-name')], $locale)) + ->line(new ListMessageLine([ + $locale->trans('notifications.detail_page_name') => $page->name, + $locale->trans('notifications.detail_created_by') => $this->user->name, + ])) + ->action($locale->trans('notifications.action_view_page'), $page->getUrl()) + ->line($this->buildReasonFooterLine($locale)); + } +} diff --git a/app/Activity/Notifications/Messages/PageUpdateNotification.php b/app/Activity/Notifications/Messages/PageUpdateNotification.php new file mode 100644 index 000000000..1c8155d29 --- /dev/null +++ b/app/Activity/Notifications/Messages/PageUpdateNotification.php @@ -0,0 +1,30 @@ +detail; + + $locale = $notifiable->getLocale(); + + return $this->newMailMessage($locale) + ->subject($locale->trans('notifications.updated_page_subject', ['pageName' => $page->getShortName()])) + ->line($locale->trans('notifications.updated_page_intro', ['appName' => setting('app-name')])) + ->line(new ListMessageLine([ + $locale->trans('notifications.detail_page_name') => $page->name, + $locale->trans('notifications.detail_updated_by') => $this->user->name, + ])) + ->line($locale->trans('notifications.updated_page_debounce')) + ->action($locale->trans('notifications.action_view_page'), $page->getUrl()) + ->line($this->buildReasonFooterLine($locale)); + } +} diff --git a/app/Activity/Notifications/NotificationManager.php b/app/Activity/Notifications/NotificationManager.php new file mode 100644 index 000000000..294f56ebb --- /dev/null +++ b/app/Activity/Notifications/NotificationManager.php @@ -0,0 +1,52 @@ +[] + */ + protected array $handlers = []; + + public function handle(Activity $activity, string|Loggable $detail, User $user): void + { + $activityType = $activity->type; + $handlersToRun = $this->handlers[$activityType] ?? []; + foreach ($handlersToRun as $handlerClass) { + /** @var NotificationHandler $handler */ + $handler = new $handlerClass(); + $handler->handle($activity, $detail, $user); + } + } + + /** + * @param class-string $handlerClass + */ + public function registerHandler(string $activityType, string $handlerClass): void + { + if (!isset($this->handlers[$activityType])) { + $this->handlers[$activityType] = []; + } + + if (!in_array($handlerClass, $this->handlers[$activityType])) { + $this->handlers[$activityType][] = $handlerClass; + } + } + + public function loadDefaultHandlers(): void + { + $this->registerHandler(ActivityType::PAGE_CREATE, PageCreationNotificationHandler::class); + $this->registerHandler(ActivityType::PAGE_UPDATE, PageUpdateNotificationHandler::class); + $this->registerHandler(ActivityType::COMMENT_CREATE, CommentCreationNotificationHandler::class); + } +} diff --git a/app/Activity/Tools/ActivityLogger.php b/app/Activity/Tools/ActivityLogger.php index 315e8bfdf..adda36c1b 100644 --- a/app/Activity/Tools/ActivityLogger.php +++ b/app/Activity/Tools/ActivityLogger.php @@ -6,7 +6,7 @@ use BookStack\Activity\DispatchWebhookJob; use BookStack\Activity\Models\Activity; use BookStack\Activity\Models\Loggable; use BookStack\Activity\Models\Webhook; -use BookStack\App\Model; +use BookStack\Activity\Notifications\NotificationManager; use BookStack\Entities\Models\Entity; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; @@ -15,10 +15,16 @@ use Illuminate\Support\Facades\Log; class ActivityLogger { + public function __construct( + protected NotificationManager $notifications + ) { + $this->notifications->loadDefaultHandlers(); + } + /** * Add a generic activity event to the database. */ - public function add(string $type, $detail = '') + public function add(string $type, string|Loggable $detail = ''): void { $detailToStore = ($detail instanceof Loggable) ? $detail->logDescriptor() : $detail; @@ -34,6 +40,7 @@ class ActivityLogger $this->setNotification($type); $this->dispatchWebhooks($type, $detail); + $this->notifications->handle($activity, $detail, user()); Theme::dispatch(ThemeEvents::ACTIVITY_LOGGED, $type, $detail); } diff --git a/app/Activity/Tools/EntityWatchers.php b/app/Activity/Tools/EntityWatchers.php new file mode 100644 index 000000000..1ab53cb1c --- /dev/null +++ b/app/Activity/Tools/EntityWatchers.php @@ -0,0 +1,86 @@ +build(); + } + + public function getWatcherUserIds(): array + { + return $this->watchers; + } + + public function isUserIgnoring(int $userId): bool + { + return in_array($userId, $this->ignorers); + } + + protected function build(): void + { + $watches = $this->getRelevantWatches(); + + // Sort before de-duping, so that the order looped below follows book -> chapter -> page ordering + usort($watches, function (Watch $watchA, Watch $watchB) { + $entityTypeDiff = $watchA->watchable_type <=> $watchB->watchable_type; + return $entityTypeDiff === 0 ? ($watchA->user_id <=> $watchB->user_id) : $entityTypeDiff; + }); + + // De-dupe by user id to get their most relevant level + $levelByUserId = []; + foreach ($watches as $watch) { + $levelByUserId[$watch->user_id] = $watch->level; + } + + // Populate the class arrays + $this->watchers = array_keys(array_filter($levelByUserId, fn(int $level) => $level >= $this->watchLevel)); + $this->ignorers = array_keys(array_filter($levelByUserId, fn(int $level) => $level === 0)); + } + + /** + * @return Watch[] + */ + protected function getRelevantWatches(): array + { + /** @var Entity[] $entitiesInvolved */ + $entitiesInvolved = array_filter([ + $this->entity, + $this->entity instanceof BookChild ? $this->entity->book : null, + $this->entity instanceof Page ? $this->entity->chapter : null, + ]); + + $query = Watch::query()->where(function (Builder $query) use ($entitiesInvolved) { + foreach ($entitiesInvolved as $entity) { + $query->orWhere(function (Builder $query) use ($entity) { + $query->where('watchable_type', '=', $entity->getMorphClass()) + ->where('watchable_id', '=', $entity->id); + }); + } + }); + + return $query->get([ + 'level', 'watchable_id', 'watchable_type', 'user_id' + ])->all(); + } +} diff --git a/app/Activity/Tools/UserEntityWatchOptions.php b/app/Activity/Tools/UserEntityWatchOptions.php new file mode 100644 index 000000000..559d7903d --- /dev/null +++ b/app/Activity/Tools/UserEntityWatchOptions.php @@ -0,0 +1,131 @@ +user->can('receive-notifications') && !$this->user->isGuest(); + } + + public function getWatchLevel(): string + { + return WatchLevels::levelValueToName($this->getWatchLevelValue()); + } + + public function isWatching(): bool + { + return $this->getWatchLevelValue() !== WatchLevels::DEFAULT; + } + + public function getWatchedParent(): ?WatchedParentDetails + { + $watchMap = $this->getWatchMap(); + unset($watchMap[$this->entity->getMorphClass()]); + + if (isset($watchMap['chapter'])) { + return new WatchedParentDetails('chapter', $watchMap['chapter']); + } + + if (isset($watchMap['book'])) { + return new WatchedParentDetails('book', $watchMap['book']); + } + + return null; + } + + public function updateLevelByName(string $level): void + { + $levelValue = WatchLevels::levelNameToValue($level); + $this->updateLevelByValue($levelValue); + } + + public function updateLevelByValue(int $level): void + { + if ($level < 0) { + $this->remove(); + return; + } + + $this->updateLevel($level); + } + + public function getWatchMap(): array + { + if (!is_null($this->watchMap)) { + return $this->watchMap; + } + + $entities = [$this->entity]; + if ($this->entity instanceof BookChild) { + $entities[] = $this->entity->book; + } + if ($this->entity instanceof Page && $this->entity->chapter) { + $entities[] = $this->entity->chapter; + } + + $query = Watch::query() + ->where('user_id', '=', $this->user->id) + ->where(function (Builder $subQuery) use ($entities) { + foreach ($entities as $entity) { + $subQuery->orWhere(function (Builder $whereQuery) use ($entity) { + $whereQuery->where('watchable_type', '=', $entity->getMorphClass()) + ->where('watchable_id', '=', $entity->id); + }); + } + }); + + $this->watchMap = $query->get(['watchable_type', 'level']) + ->pluck('level', 'watchable_type') + ->toArray(); + + return $this->watchMap; + } + + protected function getWatchLevelValue() + { + return $this->getWatchMap()[$this->entity->getMorphClass()] ?? WatchLevels::DEFAULT; + } + + protected function updateLevel(int $levelValue): void + { + Watch::query()->updateOrCreate([ + 'watchable_id' => $this->entity->id, + 'watchable_type' => $this->entity->getMorphClass(), + 'user_id' => $this->user->id, + ], [ + 'level' => $levelValue, + ]); + $this->watchMap = null; + } + + protected function remove(): void + { + $this->entityQuery()->delete(); + $this->watchMap = null; + } + + protected function entityQuery(): Builder + { + return Watch::query()->where('watchable_id', '=', $this->entity->id) + ->where('watchable_type', '=', $this->entity->getMorphClass()) + ->where('user_id', '=', $this->user->id); + } +} diff --git a/app/Activity/Tools/WatchedParentDetails.php b/app/Activity/Tools/WatchedParentDetails.php new file mode 100644 index 000000000..5a881c04f --- /dev/null +++ b/app/Activity/Tools/WatchedParentDetails.php @@ -0,0 +1,19 @@ +level === WatchLevels::IGNORE; + } +} diff --git a/app/Activity/WatchLevels.php b/app/Activity/WatchLevels.php new file mode 100644 index 000000000..de3c5e122 --- /dev/null +++ b/app/Activity/WatchLevels.php @@ -0,0 +1,91 @@ + value array. + * @returns array + */ + public static function all(): array + { + $options = []; + foreach ((new \ReflectionClass(static::class))->getConstants() as $name => $value) { + $options[strtolower($name)] = $value; + } + + return $options; + } + + /** + * Get the watch options suited for the given entity. + * @returns array + */ + public static function allSuitedFor(Entity $entity): array + { + $options = static::all(); + + if ($entity instanceof Page) { + unset($options['new']); + } elseif ($entity instanceof Bookshelf) { + return []; + } + + return $options; + } + + /** + * Convert the given name to a level value. + * Defaults to default value if the level does not exist. + */ + public static function levelNameToValue(string $level): int + { + return static::all()[$level] ?? static::DEFAULT; + } + + /** + * Convert the given int level value to a level name. + * Defaults to 'default' level name if not existing. + */ + public static function levelValueToName(int $level): string + { + foreach (static::all() as $name => $value) { + if ($level === $value) { + return $name; + } + } + + return 'default'; + } +} diff --git a/app/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index 3cd33ffa5..bffc38623 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -7,6 +7,7 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; use Illuminate\Validation\Rules\Password; @@ -27,13 +28,16 @@ class ApiDocsGenerator { $appVersion = trim(file_get_contents(base_path('version'))); $cacheKey = 'api-docs::' . $appVersion; - if (Cache::has($cacheKey) && config('app.env') === 'production') { - $docs = Cache::get($cacheKey); - } else { - $docs = (new ApiDocsGenerator())->generate(); - Cache::put($cacheKey, $docs, 60 * 24); + $isProduction = config('app.env') === 'production'; + $cacheVal = $isProduction ? Cache::get($cacheKey) : null; + + if (!is_null($cacheVal)) { + return $cacheVal; } + $docs = (new ApiDocsGenerator())->generate(); + Cache::put($cacheKey, $docs, 60 * 24); + return $docs; } diff --git a/app/Api/ApiEntityListFormatter.php b/app/Api/ApiEntityListFormatter.php index c170ecf0c..436d66d59 100644 --- a/app/Api/ApiEntityListFormatter.php +++ b/app/Api/ApiEntityListFormatter.php @@ -10,7 +10,7 @@ class ApiEntityListFormatter * The list to be formatted. * @var Entity[] */ - protected $list = []; + protected array $list = []; /** * The fields to show in the formatted data. @@ -19,9 +19,9 @@ class ApiEntityListFormatter * will be used for the resultant value. A null return value will omit the property. * @var array */ - protected $fields = [ - 'id', 'name', 'slug', 'book_id', 'chapter_id', - 'draft', 'template', 'created_at', 'updated_at', + protected array $fields = [ + 'id', 'name', 'slug', 'book_id', 'chapter_id', 'draft', + 'template', 'priority', 'created_at', 'updated_at', ]; public function __construct(array $list) diff --git a/app/App/HomeController.php b/app/App/HomeController.php index d971247df..927fdfc1d 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -78,14 +78,14 @@ class HomeController extends Controller } if ($homepageOption === 'bookshelves') { - $shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); + $shelves = app()->make(BookshelfRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); $data = array_merge($commonData, ['shelves' => $shelves]); return view('home.shelves', $data); } if ($homepageOption === 'books') { - $books = app(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); + $books = app()->make(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); $data = array_merge($commonData, ['books' => $books]); return view('home.books', $data); diff --git a/app/Notifications/MailNotification.php b/app/App/MailNotification.php similarity index 56% rename from app/Notifications/MailNotification.php rename to app/App/MailNotification.php index 12159b278..50b7f69a7 100644 --- a/app/Notifications/MailNotification.php +++ b/app/App/MailNotification.php @@ -1,16 +1,23 @@ $locale ?? user()->getLocale()]; + return (new MailMessage())->view([ 'html' => 'vendor.notifications.email', 'text' => 'vendor.notifications.email-plain', - ]); + ], $data); } } diff --git a/app/App/Providers/AppServiceProvider.php b/app/App/Providers/AppServiceProvider.php index 0c0895bf4..0275a5489 100644 --- a/app/App/Providers/AppServiceProvider.php +++ b/app/App/Providers/AppServiceProvider.php @@ -9,15 +9,15 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use BookStack\Exceptions\BookStackExceptionHandlerPage; +use BookStack\Http\HttpRequestService; +use BookStack\Permissions\PermissionApplicator; use BookStack\Settings\SettingService; use BookStack\Util\CspService; -use GuzzleHttp\Client; use Illuminate\Contracts\Foundation\ExceptionRenderer; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; -use Psr\Http\Client\ClientInterface as HttpClientInterface; class AppServiceProvider extends ServiceProvider { @@ -38,6 +38,7 @@ class AppServiceProvider extends ServiceProvider SettingService::class => SettingService::class, SocialAuthService::class => SocialAuthService::class, CspService::class => CspService::class, + HttpRequestService::class => HttpRequestService::class, ]; /** @@ -50,7 +51,7 @@ class AppServiceProvider extends ServiceProvider // Set root URL $appUrl = config('app.url'); if ($appUrl) { - $isHttps = (strpos($appUrl, 'https://') === 0); + $isHttps = str_starts_with($appUrl, 'https://'); URL::forceRootUrl($appUrl); URL::forceScheme($isHttps ? 'https' : 'http'); } @@ -74,10 +75,8 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - $this->app->bind(HttpClientInterface::class, function ($app) { - return new Client([ - 'timeout' => 3, - ]); + $this->app->singleton(PermissionApplicator::class, function ($app) { + return new PermissionApplicator(null); }); } } diff --git a/app/App/Providers/AuthServiceProvider.php b/app/App/Providers/AuthServiceProvider.php index dc482ce33..26d180310 100644 --- a/app/App/Providers/AuthServiceProvider.php +++ b/app/App/Providers/AuthServiceProvider.php @@ -9,6 +9,7 @@ use BookStack\Access\LdapService; use BookStack\Access\LoginService; use BookStack\Access\RegistrationService; use BookStack\Api\ApiTokenGuard; +use BookStack\Users\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Support\ServiceProvider; use Illuminate\Validation\Rules\Password; @@ -65,5 +66,11 @@ class AuthServiceProvider extends ServiceProvider Auth::provider('external-users', function ($app, array $config) { return new ExternalBaseUserProvider($config['model']); }); + + // Bind and provide the default system user as a singleton to the app instance when needed. + // This effectively "caches" fetching the user at an app-instance level. + $this->app->singleton('users.default', function () { + return User::query()->where('system_name', '=', 'public')->first(); + }); } } diff --git a/app/App/Providers/EventServiceProvider.php b/app/App/Providers/EventServiceProvider.php index bf502d7da..4ec9facdf 100644 --- a/app/App/Providers/EventServiceProvider.php +++ b/app/App/Providers/EventServiceProvider.php @@ -3,7 +3,12 @@ namespace BookStack\App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use SocialiteProviders\Azure\AzureExtendSocialite; +use SocialiteProviders\Discord\DiscordExtendSocialite; +use SocialiteProviders\GitLab\GitLabExtendSocialite; use SocialiteProviders\Manager\SocialiteWasCalled; +use SocialiteProviders\Okta\OktaExtendSocialite; +use SocialiteProviders\Twitch\TwitchExtendSocialite; class EventServiceProvider extends ServiceProvider { @@ -14,12 +19,11 @@ class EventServiceProvider extends ServiceProvider */ protected $listen = [ SocialiteWasCalled::class => [ - 'SocialiteProviders\Slack\SlackExtendSocialite@handle', - 'SocialiteProviders\Azure\AzureExtendSocialite@handle', - 'SocialiteProviders\Okta\OktaExtendSocialite@handle', - 'SocialiteProviders\GitLab\GitLabExtendSocialite@handle', - 'SocialiteProviders\Twitch\TwitchExtendSocialite@handle', - 'SocialiteProviders\Discord\DiscordExtendSocialite@handle', + AzureExtendSocialite::class . '@handle', + OktaExtendSocialite::class . '@handle', + GitLabExtendSocialite::class . '@handle', + TwitchExtendSocialite::class . '@handle', + DiscordExtendSocialite::class . '@handle', ], ]; diff --git a/app/App/Providers/ViewTweaksServiceProvider.php b/app/App/Providers/ViewTweaksServiceProvider.php index 16b5c1f97..10593ac8b 100644 --- a/app/App/Providers/ViewTweaksServiceProvider.php +++ b/app/App/Providers/ViewTweaksServiceProvider.php @@ -25,7 +25,7 @@ class ViewTweaksServiceProvider extends ServiceProvider // Custom blade view directives Blade::directive('icon', function ($expression) { - return ""; + return "toHtml(); ?>"; }); } } diff --git a/app/App/helpers.php b/app/App/helpers.php index 00d3e9243..af6dbcfc3 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -35,23 +35,7 @@ function versioned_asset(string $file = ''): string */ function user(): User { - return auth()->user() ?: User::getDefault(); -} - -/** - * Check if current user is a signed in user. - */ -function signedInUser(): bool -{ - return auth()->user() && !auth()->user()->isDefault(); -} - -/** - * Check if the current user has general access. - */ -function hasAppAccess(): bool -{ - return !auth()->guest() || setting('app-public'); + return auth()->user() ?: User::getGuest(); } /** @@ -61,11 +45,11 @@ function hasAppAccess(): bool function userCan(string $permission, Model $ownable = null): bool { if ($ownable === null) { - return user() && user()->can($permission); + return user()->can($permission); } // Check permission on ownable item - $permissions = app(PermissionApplicator::class); + $permissions = app()->make(PermissionApplicator::class); return $permissions->checkOwnableUserAccess($ownable, $permission); } @@ -76,7 +60,7 @@ function userCan(string $permission, Model $ownable = null): bool */ function userCanOnAny(string $action, string $entityClass = ''): bool { - $permissions = app(PermissionApplicator::class); + $permissions = app()->make(PermissionApplicator::class); return $permissions->checkUserHasEntityPermissionOnAny($action, $entityClass); } @@ -88,7 +72,7 @@ function userCanOnAny(string $action, string $entityClass = ''): bool */ function setting(string $key = null, $default = null) { - $settingService = resolve(SettingService::class); + $settingService = app()->make(SettingService::class); if (is_null($key)) { return $settingService; @@ -113,39 +97,6 @@ function theme_path(string $path = ''): ?string return base_path('themes/' . $theme . ($path ? DIRECTORY_SEPARATOR . $path : $path)); } -/** - * Get fetch an SVG icon as a string. - * Checks for icons defined within a custom theme before defaulting back - * to the 'resources/assets/icons' folder. - * - * Returns an empty string if icon file not found. - */ -function icon(string $name, array $attrs = []): string -{ - $attrs = array_merge([ - 'class' => 'svg-icon', - 'data-icon' => $name, - 'role' => 'presentation', - ], $attrs); - $attrString = ' '; - foreach ($attrs as $attrName => $attr) { - $attrString .= $attrName . '="' . $attr . '" '; - } - - $iconPath = resource_path('icons/' . $name . '.svg'); - $themeIconPath = theme_path('icons/' . $name . '.svg'); - - if ($themeIconPath && file_exists($themeIconPath)) { - $iconPath = $themeIconPath; - } elseif (!file_exists($iconPath)) { - return ''; - } - - $fileContents = file_get_contents($iconPath); - - return str_replace(' env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'), + // A list of the sources/hostnames that can be reached by application SSR calls. + // This is used wherever users can provide URLs/hosts in-platform, like for webhooks. + // Host-specific functionality (usually controlled via other options) like auth + // or user avatars for example, won't use this list. + // Space seperated if multiple. Can use '*' as a wildcard. + // Values will be compared prefix-matched, case-insensitive, against called SSR urls. + // Defaults to allow all hosts. + 'ssr_hosts' => env('ALLOWED_SSR_HOSTS', '*'), + // Alter the precision of IP addresses stored by BookStack. // Integer value between 0 (IP hidden) to 4 (Full IP usage) 'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4), @@ -74,10 +83,10 @@ return [ 'timezone' => env('APP_TIMEZONE', 'UTC'), // Default locale to use + // A default variant is also stored since Laravel can overwrite + // app.locale when dynamically setting the locale in-app. 'locale' => env('APP_LANG', 'en'), - - // Locales available - 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'el', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ka', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ro', 'ru', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'], + 'default_locale' => env('APP_LANG', 'en'), // Application Fallback Locale 'fallback_locale' => 'en', @@ -85,9 +94,6 @@ return [ // Faker Locale 'faker_locale' => 'en_GB', - // Enable right-to-left text control. - 'rtl' => false, - // Auto-detect the locale for public users // For public users their locale can be guessed by headers sent by their // browser. This is usually set by users in their browser settings. diff --git a/app/Config/mail.php b/app/Config/mail.php index 6400211e8..2906d769a 100644 --- a/app/Config/mail.php +++ b/app/Config/mail.php @@ -22,7 +22,7 @@ return [ // Global "From" address & name 'from' => [ - 'address' => env('MAIL_FROM', 'mail@bookstackapp.com'), + 'address' => env('MAIL_FROM', 'bookstack@example.com'), 'name' => env('MAIL_FROM_NAME', 'BookStack'), ], diff --git a/app/Config/oidc.php b/app/Config/oidc.php index 1f73fb688..b28b8a41a 100644 --- a/app/Config/oidc.php +++ b/app/Config/oidc.php @@ -9,7 +9,7 @@ return [ 'dump_user_details' => env('OIDC_DUMP_USER_DETAILS', false), // Claim, within an OpenId token, to find the user's display name - 'display_name_claims' => explode('|', env('OIDC_DISPLAY_NAME_CLAIMS', 'name')), + 'display_name_claims' => env('OIDC_DISPLAY_NAME_CLAIMS', 'name'), // Claim, within an OpenID token, to use to connect a BookStack user to the OIDC user. 'external_id_claim' => env('OIDC_EXTERNAL_ID_CLAIM', 'sub'), diff --git a/app/Console/Commands/CleanupImagesCommand.php b/app/Console/Commands/CleanupImagesCommand.php index fe924b0f4..18e60ff17 100644 --- a/app/Console/Commands/CleanupImagesCommand.php +++ b/app/Console/Commands/CleanupImagesCommand.php @@ -35,7 +35,7 @@ class CleanupImagesCommand extends Command if (!$dryRun) { $this->warn("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\n"); - $proceed = $this->confirm("Are you sure you want to proceed?"); + $proceed = !$this->input->isInteractive() || $this->confirm("Are you sure you want to proceed?"); if (!$proceed) { return 0; } @@ -46,7 +46,7 @@ class CleanupImagesCommand extends Command if ($dryRun) { $this->comment('Dry run, no images have been deleted'); - $this->comment($deleteCount . ' images found that would have been deleted'); + $this->comment($deleteCount . ' image(s) found that would have been deleted'); $this->showDeletedImages($deleted); $this->comment('Run with -f or --force to perform deletions'); @@ -54,7 +54,8 @@ class CleanupImagesCommand extends Command } $this->showDeletedImages($deleted); - $this->comment($deleteCount . ' images deleted'); + $this->comment("{$deleteCount} image(s) deleted"); + return 0; } @@ -65,7 +66,7 @@ class CleanupImagesCommand extends Command } if (count($paths) > 0) { - $this->line('Images to delete:'); + $this->line('Image(s) to delete:'); } foreach ($paths as $path) { diff --git a/app/Console/Commands/HandlesSingleUser.php b/app/Console/Commands/HandlesSingleUser.php new file mode 100644 index 000000000..d3014aab1 --- /dev/null +++ b/app/Console/Commands/HandlesSingleUser.php @@ -0,0 +1,40 @@ +option('id'); + $email = $this->option('email'); + if (!$id && !$email) { + throw new Exception("Either a --id= or --email= option must be provided.\nRun this command with `--help` to show more options."); + } + + $field = $id ? 'id' : 'email'; + $value = $id ?: $email; + + $user = User::query() + ->where($field, '=', $value) + ->first(); + + if (!$user) { + throw new Exception("A user where {$field}={$value} could not be found."); + } + + return $user; + } +} diff --git a/app/Console/Commands/RefreshAvatarCommand.php b/app/Console/Commands/RefreshAvatarCommand.php new file mode 100644 index 000000000..e402285e7 --- /dev/null +++ b/app/Console/Commands/RefreshAvatarCommand.php @@ -0,0 +1,116 @@ +avatarFetchEnabled()) { + $this->error("Avatar fetching is disabled on this instance."); + return self::FAILURE; + } + + if ($this->option('users-without-avatars')) { + return $this->processUsers(User::query()->whereDoesntHave('avatar')->get()->all(), $userAvatar); + } + + if ($this->option('all')) { + return $this->processUsers(User::query()->get()->all(), $userAvatar); + } + + try { + $user = $this->fetchProvidedUser(); + return $this->processUsers([$user], $userAvatar); + } catch (Exception $exception) { + $this->error($exception->getMessage()); + return self::FAILURE; + } + } + + /** + * @param User[] $users + */ + private function processUsers(array $users, UserAvatars $userAvatar): int + { + $dryRun = !$this->option('force'); + $this->info(count($users) . " user(s) found to update avatars for."); + + if (count($users) === 0) { + return self::SUCCESS; + } + + if (!$dryRun) { + $fetchHost = parse_url($userAvatar->getAvatarUrl(), PHP_URL_HOST); + $this->warn("This will destroy any existing avatar images these users have, and attempt to fetch new avatar images from {$fetchHost}."); + $proceed = !$this->input->isInteractive() || $this->confirm('Are you sure you want to proceed?'); + if (!$proceed) { + return self::SUCCESS; + } + } + + $this->info(""); + + $exitCode = self::SUCCESS; + foreach ($users as $user) { + $linePrefix = "[ID: {$user->id}] $user->email -"; + + if ($dryRun) { + $this->warn("{$linePrefix} Not updated"); + continue; + } + + if ($this->fetchAvatar($userAvatar, $user)) { + $this->info("{$linePrefix} Updated"); + } else { + $this->error("{$linePrefix} Not updated"); + $exitCode = self::FAILURE; + } + } + + if ($dryRun) { + $this->comment(""); + $this->comment("Dry run, no avatars were updated."); + $this->comment('Run with -f or --force to perform the update.'); + } + + return $exitCode; + } + + private function fetchAvatar(UserAvatars $userAvatar, User $user): bool + { + $oldId = $user->avatar->id ?? 0; + + $userAvatar->fetchAndAssignToUser($user); + + $user->refresh(); + $newId = $user->avatar->id ?? $oldId; + return $oldId !== $newId; + } +} diff --git a/app/Console/Commands/ResetMfaCommand.php b/app/Console/Commands/ResetMfaCommand.php index b8076d2d6..2b0801e39 100644 --- a/app/Console/Commands/ResetMfaCommand.php +++ b/app/Console/Commands/ResetMfaCommand.php @@ -2,11 +2,13 @@ namespace BookStack\Console\Commands; -use BookStack\Users\Models\User; +use Exception; use Illuminate\Console\Command; class ResetMfaCommand extends Command { + use HandlesSingleUser; + /** * The name and signature of the console command. * @@ -29,25 +31,10 @@ class ResetMfaCommand extends Command */ public function handle(): int { - $id = $this->option('id'); - $email = $this->option('email'); - if (!$id && !$email) { - $this->error('Either a --id= or --email= option must be provided.'); - - return 1; - } - - $field = $id ? 'id' : 'email'; - $value = $id ?: $email; - - /** @var User $user */ - $user = User::query() - ->where($field, '=', $value) - ->first(); - - if (!$user) { - $this->error("A user where {$field}={$value} could not be found."); - + try { + $user = $this->fetchProvidedUser(); + } catch (Exception $exception) { + $this->error($exception->getMessage()); return 1; } diff --git a/app/Entities/BreadcrumbsViewComposer.php b/app/Entities/BreadcrumbsViewComposer.php index 797162dfb..c9269c7c6 100644 --- a/app/Entities/BreadcrumbsViewComposer.php +++ b/app/Entities/BreadcrumbsViewComposer.php @@ -8,29 +8,21 @@ use Illuminate\View\View; class BreadcrumbsViewComposer { - protected $entityContextManager; - - /** - * BreadcrumbsViewComposer constructor. - * - * @param ShelfContext $entityContextManager - */ - public function __construct(ShelfContext $entityContextManager) - { - $this->entityContextManager = $entityContextManager; + public function __construct( + protected ShelfContext $shelfContext + ) { } /** * Modify data when the view is composed. - * - * @param View $view */ - public function compose(View $view) + public function compose(View $view): void { $crumbs = $view->getData()['crumbs']; $firstCrumb = $crumbs[0] ?? null; + if ($firstCrumb instanceof Book) { - $shelf = $this->entityContextManager->getContextualShelfForBook($firstCrumb); + $shelf = $this->shelfContext->getContextualShelfForBook($firstCrumb); if ($shelf) { array_unshift($crumbs, $shelf); $view->with('crumbs', $crumbs); diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index dcd1af5a1..55d28c684 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityQueries; use BookStack\Activity\ActivityType; use BookStack\Activity\Models\View; +use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; @@ -138,6 +139,7 @@ class BookController extends Controller 'current' => $book, 'bookChildren' => $bookChildren, 'bookParentShelves' => $bookParentShelves, + 'watchOptions' => new UserEntityWatchOptions(user(), $book), 'activity' => $activities->entityActivity($book, 20, 1), 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book), ]); diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 403c58de3..7f01e445a 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -19,12 +19,14 @@ class ChapterApiController extends ApiController 'name' => ['required', 'string', 'max:255'], 'description' => ['string', 'max:1000'], 'tags' => ['array'], + 'priority' => ['integer'], ], 'update' => [ 'book_id' => ['integer'], 'name' => ['string', 'min:1', 'max:255'], 'description' => ['string', 'max:1000'], 'tags' => ['array'], + 'priority' => ['integer'], ], ]; diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 7dcb66903..ee1df0581 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -3,6 +3,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\Models\View; +use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Entities\Tools\BookContents; @@ -81,6 +82,7 @@ class ChapterController extends Controller 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree, + 'watchOptions' => new UserEntityWatchOptions(user(), $chapter), 'pages' => $pages, 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 0e8893450..d2947f1bb 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -21,6 +21,7 @@ class PageApiController extends ApiController 'html' => ['required_without:markdown', 'string'], 'markdown' => ['required_without:html', 'string'], 'tags' => ['array'], + 'priority' => ['integer'], ], 'update' => [ 'book_id' => ['integer'], @@ -29,6 +30,7 @@ class PageApiController extends ApiController 'html' => ['string'], 'markdown' => ['string'], 'tags' => ['array'], + 'priority' => ['integer'], ], ]; diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index e96d41bb1..624931065 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\Models\View; use BookStack\Activity\Tools\CommentTree; +use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\BookContents; @@ -151,6 +152,7 @@ class PageController extends Controller 'sidebarTree' => $sidebarTree, 'commentTree' => $commentTree, 'pageNav' => $pageNav, + 'watchOptions' => new UserEntityWatchOptions(user(), $page), 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page), diff --git a/app/Entities/EntityProvider.php b/app/Entities/EntityProvider.php index 365daf7eb..3276a6c7a 100644 --- a/app/Entities/EntityProvider.php +++ b/app/Entities/EntityProvider.php @@ -37,7 +37,7 @@ class EntityProvider * Fetch all core entity types as an associated array * with their basic names as the keys. * - * @return array + * @return array */ public function all(): array { diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 496ea46b6..332510672 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -10,6 +10,7 @@ use BookStack\Activity\Models\Loggable; use BookStack\Activity\Models\Tag; use BookStack\Activity\Models\View; use BookStack\Activity\Models\Viewable; +use BookStack\Activity\Models\Watch; use BookStack\App\Model; use BookStack\App\Sluggable; use BookStack\Entities\Tools\SlugGenerator; @@ -330,6 +331,14 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable ->exists(); } + /** + * Get the related watches for this entity. + */ + public function watches(): MorphMany + { + return $this->morphMany(Watch::class, 'watchable'); + } + /** * {@inheritdoc} */ diff --git a/app/Entities/Queries/RecentlyViewed.php b/app/Entities/Queries/RecentlyViewed.php index ffa346888..5895b97a2 100644 --- a/app/Entities/Queries/RecentlyViewed.php +++ b/app/Entities/Queries/RecentlyViewed.php @@ -10,7 +10,7 @@ class RecentlyViewed extends EntityQuery public function run(int $count, int $page): Collection { $user = user(); - if ($user === null || $user->isDefault()) { + if ($user === null || $user->isGuest()) { return collect(); } diff --git a/app/Entities/Queries/TopFavourites.php b/app/Entities/Queries/TopFavourites.php index 3f8d2e62e..a2f8d9ea1 100644 --- a/app/Entities/Queries/TopFavourites.php +++ b/app/Entities/Queries/TopFavourites.php @@ -10,7 +10,7 @@ class TopFavourites extends EntityQuery public function run(int $count, int $skip = 0) { $user = user(); - if (is_null($user) || $user->isDefault()) { + if ($user->isGuest()) { return collect(); } diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index 588854c7e..977193d85 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -16,14 +16,9 @@ use Exception; class ChapterRepo { - protected $baseRepo; - - /** - * ChapterRepo constructor. - */ - public function __construct(BaseRepo $baseRepo) - { - $this->baseRepo = $baseRepo; + public function __construct( + protected BaseRepo $baseRepo + ) { } /** diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 521519dc0..61a1db63e 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -23,24 +23,12 @@ use Illuminate\Pagination\LengthAwarePaginator; class PageRepo { - protected BaseRepo $baseRepo; - protected RevisionRepo $revisionRepo; - protected ReferenceStore $referenceStore; - protected ReferenceUpdater $referenceUpdater; - - /** - * PageRepo constructor. - */ public function __construct( - BaseRepo $baseRepo, - RevisionRepo $revisionRepo, - ReferenceStore $referenceStore, - ReferenceUpdater $referenceUpdater + protected BaseRepo $baseRepo, + protected RevisionRepo $revisionRepo, + protected ReferenceStore $referenceStore, + protected ReferenceUpdater $referenceUpdater ) { - $this->baseRepo = $baseRepo; - $this->revisionRepo = $revisionRepo; - $this->referenceStore = $referenceStore; - $this->referenceUpdater = $referenceUpdater; } /** @@ -159,13 +147,11 @@ class PageRepo */ public function publishDraft(Page $draft, array $input): Page { - $this->updateTemplateStatusAndContentFromInput($draft, $input); - $this->baseRepo->update($draft, $input); - $draft->draft = false; $draft->revision_count = 1; $draft->priority = $this->getNewPriority($draft); - $draft->save(); + $this->updateTemplateStatusAndContentFromInput($draft, $input); + $this->baseRepo->update($draft, $input); $this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision')); $this->referenceStore->updateForPage($draft); diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index 9e4d63cf7..6779797d1 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -16,18 +16,11 @@ use Throwable; class ExportFormatter { - protected ImageService $imageService; - protected PdfGenerator $pdfGenerator; - protected CspService $cspService; - - /** - * ExportService constructor. - */ - public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator, CspService $cspService) - { - $this->imageService = $imageService; - $this->pdfGenerator = $pdfGenerator; - $this->cspService = $cspService; + public function __construct( + protected ImageService $imageService, + protected PdfGenerator $pdfGenerator, + protected CspService $cspService + ) { } /** @@ -36,13 +29,14 @@ class ExportFormatter * * @throws Throwable */ - public function pageToContainedHtml(Page $page) + public function pageToContainedHtml(Page $page): string { $page->html = (new PageContent($page))->render(); $pageHtml = view('exports.page', [ 'page' => $page, 'format' => 'html', 'cspContent' => $this->cspService->getCspMetaTagValue(), + 'locale' => user()->getLocale(), ])->render(); return $this->containHtml($pageHtml); @@ -53,7 +47,7 @@ class ExportFormatter * * @throws Throwable */ - public function chapterToContainedHtml(Chapter $chapter) + public function chapterToContainedHtml(Chapter $chapter): string { $pages = $chapter->getVisiblePages(); $pages->each(function ($page) { @@ -64,6 +58,7 @@ class ExportFormatter 'pages' => $pages, 'format' => 'html', 'cspContent' => $this->cspService->getCspMetaTagValue(), + 'locale' => user()->getLocale(), ])->render(); return $this->containHtml($html); @@ -74,7 +69,7 @@ class ExportFormatter * * @throws Throwable */ - public function bookToContainedHtml(Book $book) + public function bookToContainedHtml(Book $book): string { $bookTree = (new BookContents($book))->getTree(false, true); $html = view('exports.book', [ @@ -82,6 +77,7 @@ class ExportFormatter 'bookChildren' => $bookTree, 'format' => 'html', 'cspContent' => $this->cspService->getCspMetaTagValue(), + 'locale' => user()->getLocale(), ])->render(); return $this->containHtml($html); @@ -92,13 +88,14 @@ class ExportFormatter * * @throws Throwable */ - public function pageToPdf(Page $page) + public function pageToPdf(Page $page): string { $page->html = (new PageContent($page))->render(); $html = view('exports.page', [ 'page' => $page, 'format' => 'pdf', 'engine' => $this->pdfGenerator->getActiveEngine(), + 'locale' => user()->getLocale(), ])->render(); return $this->htmlToPdf($html); @@ -109,7 +106,7 @@ class ExportFormatter * * @throws Throwable */ - public function chapterToPdf(Chapter $chapter) + public function chapterToPdf(Chapter $chapter): string { $pages = $chapter->getVisiblePages(); $pages->each(function ($page) { @@ -121,6 +118,7 @@ class ExportFormatter 'pages' => $pages, 'format' => 'pdf', 'engine' => $this->pdfGenerator->getActiveEngine(), + 'locale' => user()->getLocale(), ])->render(); return $this->htmlToPdf($html); @@ -131,7 +129,7 @@ class ExportFormatter * * @throws Throwable */ - public function bookToPdf(Book $book) + public function bookToPdf(Book $book): string { $bookTree = (new BookContents($book))->getTree(false, true); $html = view('exports.book', [ @@ -139,6 +137,7 @@ class ExportFormatter 'bookChildren' => $bookTree, 'format' => 'pdf', 'engine' => $this->pdfGenerator->getActiveEngine(), + 'locale' => user()->getLocale(), ])->render(); return $this->htmlToPdf($html); @@ -194,7 +193,7 @@ class ExportFormatter /** @var DOMElement $iframe */ foreach ($iframes as $iframe) { $link = $iframe->getAttribute('src'); - if (strpos($link, '//') === 0) { + if (str_starts_with($link, '//')) { $link = 'https:' . $link; } @@ -240,7 +239,7 @@ class ExportFormatter foreach ($linksOutput[0] as $index => $linkMatch) { $oldLinkString = $linkMatch; $srcString = $linksOutput[2][$index]; - if (strpos(trim($srcString), 'http') !== 0) { + if (!str_starts_with(trim($srcString), 'http')) { $newSrcString = url($srcString); $newLinkString = str_replace($srcString, $newSrcString, $oldLinkString); $htmlContent = str_replace($oldLinkString, $newLinkString, $htmlContent); diff --git a/app/Entities/Tools/MixedEntityRequestHelper.php b/app/Entities/Tools/MixedEntityRequestHelper.php new file mode 100644 index 000000000..8319f6aa0 --- /dev/null +++ b/app/Entities/Tools/MixedEntityRequestHelper.php @@ -0,0 +1,39 @@ +entities->get($requestData['type']); + + return $entityType->newQuery()->scopes(['visible'])->findOrFail($requestData['id']); + } + + /** + * Get the validation rules for an abstract entity request. + * @return array{type: string[], id: string[]} + */ + public function validationRules(): array + { + return [ + 'type' => ['required', 'string'], + 'id' => ['required', 'integer'], + ]; + } +} diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index 7341a0328..08276230c 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -197,7 +197,7 @@ class TrashCan $page->allRevisions()->delete(); // Delete Attached Files - $attachmentService = app(AttachmentService::class); + $attachmentService = app()->make(AttachmentService::class); foreach ($page->attachments as $attachment) { $attachmentService->deleteFile($attachment); } @@ -376,6 +376,7 @@ class TrashCan $entity->searchTerms()->delete(); $entity->deletions()->delete(); $entity->favourites()->delete(); + $entity->watches()->delete(); $entity->referencesTo()->delete(); $entity->referencesFrom()->delete(); diff --git a/app/Exceptions/ThemeException.php b/app/Exceptions/ThemeException.php new file mode 100644 index 000000000..b721effe2 --- /dev/null +++ b/app/Exceptions/ThemeException.php @@ -0,0 +1,7 @@ +isGuest()) { + $this->showPermissionError(); + } + } + /** * Check the current user's permissions against an ownable item otherwise throw an exception. */ diff --git a/app/Http/HttpClientHistory.php b/app/Http/HttpClientHistory.php new file mode 100644 index 000000000..f224b1779 --- /dev/null +++ b/app/Http/HttpClientHistory.php @@ -0,0 +1,33 @@ +container); + } + + public function requestAt(int $index): ?GuzzleRequest + { + return $this->container[$index]['request'] ?? null; + } + + public function latestRequest(): ?GuzzleRequest + { + return $this->requestAt($this->requestCount() - 1); + } + + public function all(): array + { + return $this->container; + } +} diff --git a/app/Http/HttpRequestService.php b/app/Http/HttpRequestService.php new file mode 100644 index 000000000..f59c298a6 --- /dev/null +++ b/app/Http/HttpRequestService.php @@ -0,0 +1,70 @@ + $timeout, + 'handler' => $this->handler, + ]; + + return new Client(array_merge($options, $defaultOptions)); + } + + /** + * Create a new JSON http request for use with a client. + */ + public function jsonRequest(string $method, string $uri, array $data): GuzzleRequest + { + $headers = ['Content-Type' => 'application/json']; + return new GuzzleRequest($method, $uri, $headers, json_encode($data)); + } + + /** + * Mock any http clients built from this service, and response with the given responses. + * Returns history which can then be queried. + * @link https://docs.guzzlephp.org/en/stable/testing.html#history-middleware + */ + public function mockClient(array $responses = [], bool $pad = true): HttpClientHistory + { + // By default, we pad out the responses with 10 successful values so that requests will be + // properly recorded for inspection. Otherwise, we can't later check if we're received + // too many requests. + if ($pad) { + $response = new Response(200, [], 'success'); + $responses = array_merge($responses, array_fill(0, 10, $response)); + } + + $container = []; + $history = Middleware::history($container); + $mock = new MockHandler($responses); + $this->handler = HandlerStack::create($mock); + $this->handler->push($history, 'history'); + + return new HttpClientHistory($container); + } + + /** + * Clear mocking that has been set up for clients. + */ + public function clearMocking(): void + { + $this->handler = null; + } +} diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index b348473cf..5f3ad3168 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -31,7 +31,7 @@ class ApiAuthenticate { // Return if the user is already found to be signed in via session-based auth. // This is to make it easy to browser the API via browser after just logging into the system. - if (signedInUser() || session()->isStarted()) { + if (!user()->isGuest() || session()->isStarted()) { if (!$this->sessionUserHasApiAccess()) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); } @@ -53,6 +53,6 @@ class ApiAuthenticate { $hasApiPermission = user()->can('access-api'); - return $hasApiPermission && hasAppAccess(); + return $hasApiPermission && user()->hasAppAccess(); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index a32029112..6a5c6e354 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -12,7 +12,7 @@ class Authenticate */ public function handle(Request $request, Closure $next) { - if (!hasAppAccess()) { + if (!user()->hasAppAccess()) { if ($request->ajax()) { return response('Unauthorized.', 401); } diff --git a/app/Http/Middleware/Localization.php b/app/Http/Middleware/Localization.php index 47723e242..94d06a627 100644 --- a/app/Http/Middleware/Localization.php +++ b/app/Http/Middleware/Localization.php @@ -2,17 +2,14 @@ namespace BookStack\Http\Middleware; -use BookStack\Translation\LanguageManager; -use Carbon\Carbon; +use BookStack\Translation\LocaleManager; use Closure; class Localization { - protected LanguageManager $languageManager; - - public function __construct(LanguageManager $languageManager) - { - $this->languageManager = $languageManager; + public function __construct( + protected LocaleManager $localeManager + ) { } /** @@ -25,22 +22,12 @@ class Localization */ public function handle($request, Closure $next) { - // Get and record the default language in the config - $defaultLang = config('app.locale'); - config()->set('app.default_locale', $defaultLang); + // Share details of the user's locale for use in views + $userLocale = $this->localeManager->getForUser(user()); + view()->share('locale', $userLocale); - // Get the user's language and record that in the config for use in views - $userLang = $this->languageManager->getUserLanguage($request, $defaultLang); - config()->set('app.lang', str_replace('_', '-', $this->languageManager->getIsoName($userLang))); - - // Set text direction - if ($this->languageManager->isRTL($userLang)) { - config()->set('app.rtl', true); - } - - app()->setLocale($userLang); - Carbon::setLocale($userLang); - $this->languageManager->setPhpDateTimeLocale($userLang); + // Set locale for system components + app()->setLocale($userLocale->appLocale()); return $next($request); } diff --git a/app/Http/Middleware/PreventAuthenticatedResponseCaching.php b/app/Http/Middleware/PreventAuthenticatedResponseCaching.php index 60c913811..0a90ddd9e 100644 --- a/app/Http/Middleware/PreventAuthenticatedResponseCaching.php +++ b/app/Http/Middleware/PreventAuthenticatedResponseCaching.php @@ -20,7 +20,7 @@ class PreventAuthenticatedResponseCaching /** @var Response $response */ $response = $next($request); - if (signedInUser()) { + if (!user()->isGuest()) { $response->headers->set('Cache-Control', 'max-age=0, no-store, private'); $response->headers->set('Pragma', 'no-cache'); $response->headers->set('Expires', 'Sun, 12 Jul 2015 19:01:00 GMT'); diff --git a/app/Notifications/ConfirmEmail.php b/app/Notifications/ConfirmEmail.php deleted file mode 100644 index 5399c25a8..000000000 --- a/app/Notifications/ConfirmEmail.php +++ /dev/null @@ -1,36 +0,0 @@ -token = $token; - } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail($notifiable) - { - $appName = ['appName' => setting('app-name')]; - - return $this->newMailMessage() - ->subject(trans('auth.email_confirm_subject', $appName)) - ->greeting(trans('auth.email_confirm_greeting', $appName)) - ->line(trans('auth.email_confirm_text')) - ->action(trans('auth.email_confirm_action'), url('/register/confirm/' . $this->token)); - } -} diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php deleted file mode 100644 index 7fa146596..000000000 --- a/app/Notifications/ResetPassword.php +++ /dev/null @@ -1,37 +0,0 @@ -token = $token; - } - - /** - * Build the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - return $this->newMailMessage() - ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')])) - ->line(trans('auth.email_reset_text')) - ->action(trans('auth.reset_password'), url('password/reset/' . $this->token)) - ->line(trans('auth.email_reset_not_requested')); - } -} diff --git a/app/Notifications/UserInvite.php b/app/Notifications/UserInvite.php deleted file mode 100644 index 8804df586..000000000 --- a/app/Notifications/UserInvite.php +++ /dev/null @@ -1,34 +0,0 @@ -token = $token; - } - - /** - * Get the mail representation of the notification. - */ - public function toMail(User $notifiable): MailMessage - { - $appName = ['appName' => setting('app-name')]; - $language = setting()->getUser($notifiable, 'language'); - - return $this->newMailMessage() - ->subject(trans('auth.user_invite_email_subject', $appName, $language)) - ->greeting(trans('auth.user_invite_email_greeting', $appName, $language)) - ->line(trans('auth.user_invite_email_text', [], $language)) - ->action(trans('auth.user_invite_email_action', [], $language), url('/register/invite/' . $this->token)); - } -} diff --git a/app/Permissions/PermissionApplicator.php b/app/Permissions/PermissionApplicator.php index b4fafaa9e..7b62ac0a7 100644 --- a/app/Permissions/PermissionApplicator.php +++ b/app/Permissions/PermissionApplicator.php @@ -3,19 +3,25 @@ namespace BookStack\Permissions; use BookStack\App\Model; +use BookStack\Entities\EntityProvider; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Permissions\Models\EntityPermission; use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\HasOwner; -use BookStack\Users\Models\Role; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Builder as QueryBuilder; +use Illuminate\Database\Query\JoinClause; use InvalidArgumentException; class PermissionApplicator { + public function __construct( + protected ?User $user = null + ) { + } + /** * Checks if an entity has a restriction set upon it. * @@ -143,6 +149,42 @@ class PermissionApplicator }); } + /** + * Filter out items that have related entity relations where + * the entity is marked as deleted. + */ + public function filterDeletedFromEntityRelationQuery(Builder $query, string $tableName, string $entityIdColumn, string $entityTypeColumn): Builder + { + $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; + $entityProvider = new EntityProvider(); + + $joinQuery = function ($query) use ($entityProvider) { + $first = true; + /** @var Builder $query */ + foreach ($entityProvider->all() as $entity) { + $entityQuery = function ($query) use ($entity) { + /** @var Builder $query */ + $query->select(['id', 'deleted_at']) + ->selectRaw("'{$entity->getMorphClass()}' as type") + ->from($entity->getTable()) + ->whereNotNull('deleted_at'); + }; + + if ($first) { + $entityQuery($query); + $first = false; + } else { + $query->union($entityQuery); + } + } + }; + + return $query->leftJoinSub($joinQuery, 'deletions', function (JoinClause $join) use ($tableDetails) { + $join->on($tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'], '=', 'deletions.id') + ->on($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', 'deletions.type'); + })->whereNull('deletions.deleted_at'); + } + /** * Add conditions to a query for a model that's a relation of a page, so only the model results * on visible pages are returned by the query. @@ -173,7 +215,7 @@ class PermissionApplicator */ protected function currentUser(): User { - return user(); + return $this->user ?? user(); } /** diff --git a/app/Permissions/PermissionsRepo.php b/app/Permissions/PermissionsRepo.php index 889a6ea08..b41612968 100644 --- a/app/Permissions/PermissionsRepo.php +++ b/app/Permissions/PermissionsRepo.php @@ -12,12 +12,11 @@ use Illuminate\Database\Eloquent\Collection; class PermissionsRepo { - protected JointPermissionBuilder $permissionBuilder; protected array $systemRoles = ['admin', 'public']; - public function __construct(JointPermissionBuilder $permissionBuilder) - { - $this->permissionBuilder = $permissionBuilder; + public function __construct( + protected JointPermissionBuilder $permissionBuilder + ) { } /** diff --git a/app/Search/SearchOptions.php b/app/Search/SearchOptions.php index 0bf9c3116..af146d5fd 100644 --- a/app/Search/SearchOptions.php +++ b/app/Search/SearchOptions.php @@ -44,8 +44,8 @@ class SearchOptions $inputs = $request->only(['search', 'types', 'filters', 'exact', 'tags']); $parsedStandardTerms = static::parseStandardTermString($inputs['search'] ?? ''); - $instance->searches = $parsedStandardTerms['terms']; - $instance->exacts = $parsedStandardTerms['exacts']; + $instance->searches = array_filter($parsedStandardTerms['terms']); + $instance->exacts = array_filter($parsedStandardTerms['exacts']); array_push($instance->exacts, ...array_filter($inputs['exact'] ?? [])); @@ -78,7 +78,7 @@ class SearchOptions ]; $patterns = [ - 'exacts' => '/"(.*?)"/', + 'exacts' => '/"(.*?)(? '/\[(.*?)\]/', 'filters' => '/\{(.*?)\}/', ]; @@ -93,6 +93,11 @@ class SearchOptions } } + // Unescape exacts + foreach ($terms['exacts'] as $index => $exact) { + $terms['exacts'][$index] = str_replace('\"', '"', $exact); + } + // Parse standard terms $parsedStandardTerms = static::parseStandardTermString($searchString); array_push($terms['searches'], ...$parsedStandardTerms['terms']); @@ -106,12 +111,19 @@ class SearchOptions } $terms['filters'] = $splitFilters; + // Filter down terms where required + $terms['exacts'] = array_filter($terms['exacts']); + $terms['searches'] = array_filter($terms['searches']); + return $terms; } /** * Parse a standard search term string into individual search terms and - * extract any exact terms searches to be made. + * convert any required terms to exact matches. This is done since some + * characters will never be in the standard index, since we use them as + * delimiters, and therefore we convert a term to be exact if it + * contains one of those delimiter characters. * * @return array{terms: array, exacts: array} */ @@ -129,8 +141,8 @@ class SearchOptions continue; } - $parsedList = (strpbrk($searchTerm, $indexDelimiters) === false) ? 'terms' : 'exacts'; - $parsed[$parsedList][] = $searchTerm; + $becomeExact = (strpbrk($searchTerm, $indexDelimiters) !== false); + $parsed[$becomeExact ? 'exacts' : 'terms'][] = $searchTerm; } return $parsed; @@ -141,20 +153,21 @@ class SearchOptions */ public function toString(): string { - $string = implode(' ', $this->searches ?? []); + $parts = $this->searches; foreach ($this->exacts as $term) { - $string .= ' "' . $term . '"'; + $escaped = str_replace('"', '\"', $term); + $parts[] = '"' . $escaped . '"'; } foreach ($this->tags as $term) { - $string .= " [{$term}]"; + $parts[] = "[{$term}]"; } foreach ($this->filters as $filterName => $filterVal) { - $string .= ' {' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}'; + $parts[] = '{' . $filterName . ($filterVal ? ':' . $filterVal : '') . '}'; } - return $string; + return implode(' ', $parts); } } diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index 9c48a4323..60e5fee28 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -5,7 +5,6 @@ namespace BookStack\Settings; use BookStack\Activity\ActivityType; use BookStack\Entities\Tools\TrashCan; use BookStack\Http\Controller; -use BookStack\Notifications\TestEmail; use BookStack\References\ReferenceStore; use BookStack\Uploads\ImageService; use Illuminate\Http\Request; @@ -69,7 +68,7 @@ class MaintenanceController extends Controller $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'send-test-email'); try { - user()->notifyNow(new TestEmail()); + user()->notifyNow(new TestEmailNotification()); $this->showSuccessNotification(trans('settings.maint_send_test_email_success', ['address' => user()->email])); } catch (\Exception $exception) { $errorMessage = trans('errors.maintenance_test_email_failure') . "\n" . $exception->getMessage(); diff --git a/app/Settings/SettingController.php b/app/Settings/SettingController.php index bd55222f2..bdbc3c78a 100644 --- a/app/Settings/SettingController.php +++ b/app/Settings/SettingController.php @@ -34,7 +34,7 @@ class SettingController extends Controller return view('settings.' . $category, [ 'category' => $category, 'version' => $version, - 'guestUser' => User::getDefault(), + 'guestUser' => User::getGuest(), ]); } diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index 1c5cbdf5a..31debdaea 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -47,7 +47,7 @@ class SettingService $default = config('setting-defaults.user.' . $key, false); } - if ($user->isDefault()) { + if ($user->isGuest()) { return $this->getFromSession($key, $default); } @@ -206,7 +206,7 @@ class SettingService */ public function putUser(User $user, string $key, string $value): bool { - if ($user->isDefault()) { + if ($user->isGuest()) { session()->put($key, $value); return true; diff --git a/app/Settings/StatusController.php b/app/Settings/StatusController.php index 5193bc50d..e4321bf30 100644 --- a/app/Settings/StatusController.php +++ b/app/Settings/StatusController.php @@ -20,10 +20,11 @@ class StatusController extends Controller return DB::table('migrations')->count() > 0; }), 'cache' => $this->trueWithoutError(function () { - $rand = Str::random(); - Cache::add('status_test', $rand); + $rand = Str::random(12); + $key = "status_test_{$rand}"; + Cache::add($key, $rand); - return Cache::pull('status_test') === $rand; + return Cache::pull($key) === $rand; }), 'session' => $this->trueWithoutError(function () { $rand = Str::random(); diff --git a/app/Notifications/TestEmail.php b/app/Settings/TestEmailNotification.php similarity index 50% rename from app/Notifications/TestEmail.php rename to app/Settings/TestEmailNotification.php index 7f59ff70f..3b13543e4 100644 --- a/app/Notifications/TestEmail.php +++ b/app/Settings/TestEmailNotification.php @@ -1,17 +1,14 @@ newMailMessage() ->subject(trans('settings.maint_send_test_email_mail_subject')) diff --git a/app/Settings/UserNotificationPreferences.php b/app/Settings/UserNotificationPreferences.php new file mode 100644 index 000000000..5b267b533 --- /dev/null +++ b/app/Settings/UserNotificationPreferences.php @@ -0,0 +1,46 @@ +getNotificationSetting('own-page-changes'); + } + + public function notifyOnOwnPageComments(): bool + { + return $this->getNotificationSetting('own-page-comments'); + } + + public function notifyOnCommentReplies(): bool + { + return $this->getNotificationSetting('comment-replies'); + } + + public function updateFromSettingsArray(array $settings) + { + $allowList = ['own-page-changes', 'own-page-comments', 'comment-replies']; + foreach ($settings as $setting => $status) { + if (!in_array($setting, $allowList)) { + continue; + } + + $value = $status === 'true' ? 'true' : 'false'; + setting()->putUser($this->user, 'notifications#' . $setting, $value); + } + } + + protected function getNotificationSetting(string $key): bool + { + return setting()->getUser($this->user, 'notifications#' . $key); + } +} diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index bb91643e3..31a7d3c64 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -3,19 +3,23 @@ namespace BookStack\Theming; use BookStack\Access\SocialAuthService; +use BookStack\Exceptions\ThemeException; use Illuminate\Console\Application; use Illuminate\Console\Application as Artisan; use Symfony\Component\Console\Command\Command; class ThemeService { - protected $listeners = []; + /** + * @var array + */ + protected array $listeners = []; /** * Listen to a given custom theme event, * setting up the action to be ran when the event occurs. */ - public function listen(string $event, callable $action) + public function listen(string $event, callable $action): void { if (!isset($this->listeners[$event])) { $this->listeners[$event] = []; @@ -31,10 +35,8 @@ class ThemeService * * If a callback returns a non-null value, this method will * stop and return that value itself. - * - * @return mixed */ - public function dispatch(string $event, ...$args) + public function dispatch(string $event, ...$args): mixed { foreach ($this->listeners[$event] ?? [] as $action) { $result = call_user_func_array($action, $args); @@ -49,7 +51,7 @@ class ThemeService /** * Register a new custom artisan command to be available. */ - public function registerCommand(Command $command) + public function registerCommand(Command $command): void { Artisan::starting(function (Application $application) use ($command) { $application->addCommands([$command]); @@ -59,18 +61,22 @@ class ThemeService /** * Read any actions from the set theme path if the 'functions.php' file exists. */ - public function readThemeActions() + public function readThemeActions(): void { $themeActionsFile = theme_path('functions.php'); if ($themeActionsFile && file_exists($themeActionsFile)) { - require $themeActionsFile; + try { + require $themeActionsFile; + } catch (\Error $exception) { + throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}"); + } } } /** * @see SocialAuthService::addSocialDriver */ - public function addSocialDriver(string $driverName, array $config, string $socialiteHandler, callable $configureForRedirect = null) + public function addSocialDriver(string $driverName, array $config, string $socialiteHandler, callable $configureForRedirect = null): void { $socialAuthService = app()->make(SocialAuthService::class); $socialAuthService->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect); diff --git a/app/Translation/LanguageManager.php b/app/Translation/LanguageManager.php deleted file mode 100644 index 39dee8461..000000000 --- a/app/Translation/LanguageManager.php +++ /dev/null @@ -1,137 +0,0 @@ - - */ - protected array $localeMap = [ - 'ar' => ['iso' => 'ar', 'windows' => 'Arabic'], - 'bg' => ['iso' => 'bg_BG', 'windows' => 'Bulgarian'], - 'bs' => ['iso' => 'bs_BA', 'windows' => 'Bosnian (Latin)'], - 'ca' => ['iso' => 'ca', 'windows' => 'Catalan'], - 'cs' => ['iso' => 'cs_CZ', 'windows' => 'Czech'], - 'da' => ['iso' => 'da_DK', 'windows' => 'Danish'], - 'de' => ['iso' => 'de_DE', 'windows' => 'German'], - 'de_informal' => ['iso' => 'de_DE', 'windows' => 'German'], - 'en' => ['iso' => 'en_GB', 'windows' => 'English'], - 'el' => ['iso' => 'el_GR', 'windows' => 'Greek'], - 'es' => ['iso' => 'es_ES', 'windows' => 'Spanish'], - 'es_AR' => ['iso' => 'es_AR', 'windows' => 'Spanish'], - 'et' => ['iso' => 'et_EE', 'windows' => 'Estonian'], - 'eu' => ['iso' => 'eu_ES', 'windows' => 'Basque'], - 'fa' => ['iso' => 'fa_IR', 'windows' => 'Persian'], - 'fr' => ['iso' => 'fr_FR', 'windows' => 'French'], - 'he' => ['iso' => 'he_IL', 'windows' => 'Hebrew'], - 'hr' => ['iso' => 'hr_HR', 'windows' => 'Croatian'], - 'hu' => ['iso' => 'hu_HU', 'windows' => 'Hungarian'], - 'id' => ['iso' => 'id_ID', 'windows' => 'Indonesian'], - 'it' => ['iso' => 'it_IT', 'windows' => 'Italian'], - 'ja' => ['iso' => 'ja', 'windows' => 'Japanese'], - 'ko' => ['iso' => 'ko_KR', 'windows' => 'Korean'], - 'lt' => ['iso' => 'lt_LT', 'windows' => 'Lithuanian'], - 'lv' => ['iso' => 'lv_LV', 'windows' => 'Latvian'], - 'nl' => ['iso' => 'nl_NL', 'windows' => 'Dutch'], - 'nb' => ['iso' => 'nb_NO', 'windows' => 'Norwegian (Bokmal)'], - 'pl' => ['iso' => 'pl_PL', 'windows' => 'Polish'], - 'pt' => ['iso' => 'pt_PT', 'windows' => 'Portuguese'], - 'pt_BR' => ['iso' => 'pt_BR', 'windows' => 'Portuguese'], - 'ro' => ['iso' => 'ro_RO', 'windows' => 'Romanian'], - 'ru' => ['iso' => 'ru', 'windows' => 'Russian'], - 'sk' => ['iso' => 'sk_SK', 'windows' => 'Slovak'], - 'sl' => ['iso' => 'sl_SI', 'windows' => 'Slovenian'], - 'sv' => ['iso' => 'sv_SE', 'windows' => 'Swedish'], - 'uk' => ['iso' => 'uk_UA', 'windows' => 'Ukrainian'], - 'vi' => ['iso' => 'vi_VN', 'windows' => 'Vietnamese'], - 'zh_CN' => ['iso' => 'zh_CN', 'windows' => 'Chinese (Simplified)'], - 'zh_TW' => ['iso' => 'zh_TW', 'windows' => 'Chinese (Traditional)'], - 'tr' => ['iso' => 'tr_TR', 'windows' => 'Turkish'], - ]; - - /** - * Get the language specifically for the currently logged-in user if available. - */ - public function getUserLanguage(Request $request, string $default): string - { - try { - $user = user(); - } catch (\Exception $exception) { - return $default; - } - - if ($user->isDefault() && config('app.auto_detect_locale')) { - return $this->autoDetectLocale($request, $default); - } - - return setting()->getUser($user, 'language', $default); - } - - /** - * Check if the given BookStack language value is a right-to-left language. - */ - public function isRTL(string $language): bool - { - return in_array($language, $this->rtlLanguages); - } - - /** - * Autodetect the visitors locale by matching locales in their headers - * against the locales supported by BookStack. - */ - protected function autoDetectLocale(Request $request, string $default): string - { - $availableLocales = config('app.locales'); - foreach ($request->getLanguages() as $lang) { - if (in_array($lang, $availableLocales)) { - return $lang; - } - } - - return $default; - } - - /** - * Get the ISO version of a BookStack language name. - */ - public function getIsoName(string $language): string - { - return $this->localeMap[$language]['iso'] ?? $language; - } - - /** - * Set the system date locale for localized date formatting. - * Will try both the standard locale name and the UTF8 variant. - */ - public function setPhpDateTimeLocale(string $language): void - { - $isoLang = $this->localeMap[$language]['iso'] ?? ''; - $isoLangPrefix = explode('_', $isoLang)[0]; - - $locales = array_values(array_filter([ - $isoLang ? $isoLang . '.utf8' : false, - $isoLang ?: false, - $isoLang ? str_replace('_', '-', $isoLang) : false, - $isoLang ? $isoLangPrefix . '.UTF-8' : false, - $this->localeMap[$language]['windows'] ?? false, - $language, - ])); - - if (!empty($locales)) { - setlocale(LC_TIME, $locales[0], ...array_slice($locales, 1)); - } - } -} diff --git a/app/Translation/LocaleDefinition.php b/app/Translation/LocaleDefinition.php new file mode 100644 index 000000000..85d36afa0 --- /dev/null +++ b/app/Translation/LocaleDefinition.php @@ -0,0 +1,53 @@ +appName; + } + + /** + * Provide the ISO-aligned locale name. + */ + public function isoLocale(): string + { + return $this->isoName; + } + + /** + * Returns a string suitable for the HTML "lang" attribute. + */ + public function htmlLang(): string + { + return str_replace('_', '-', $this->isoName); + } + + /** + * Returns a string suitable for the HTML "dir" attribute. + */ + public function htmlDirection(): string + { + return $this->isRtl ? 'rtl' : 'ltr'; + } + + /** + * Translate using this locate. + */ + public function trans(string $key, array $replace = []): string + { + return trans($key, $replace, $this->appLocale()); + } +} diff --git a/app/Translation/LocaleManager.php b/app/Translation/LocaleManager.php new file mode 100644 index 000000000..825eae7a4 --- /dev/null +++ b/app/Translation/LocaleManager.php @@ -0,0 +1,119 @@ + + */ + protected array $localeMap = [ + 'ar' => 'ar', + 'bg' => 'bg_BG', + 'bs' => 'bs_BA', + 'ca' => 'ca', + 'cs' => 'cs_CZ', + 'cy' => 'cy_GB', + 'da' => 'da_DK', + 'de' => 'de_DE', + 'de_informal' => 'de_DE', + 'el' => 'el_GR', + 'en' => 'en_GB', + 'es' => 'es_ES', + 'es_AR' => 'es_AR', + 'et' => 'et_EE', + 'eu' => 'eu_ES', + 'fa' => 'fa_IR', + 'fr' => 'fr_FR', + 'he' => 'he_IL', + 'hr' => 'hr_HR', + 'hu' => 'hu_HU', + 'id' => 'id_ID', + 'it' => 'it_IT', + 'ja' => 'ja', + 'ka' => 'ka_GE', + 'ko' => 'ko_KR', + 'lt' => 'lt_LT', + 'lv' => 'lv_LV', + 'nb' => 'nb_NO', + 'nl' => 'nl_NL', + 'pl' => 'pl_PL', + 'pt' => 'pt_PT', + 'pt_BR' => 'pt_BR', + 'ro' => 'ro_RO', + 'ru' => 'ru', + 'sk' => 'sk_SK', + 'sl' => 'sl_SI', + 'sv' => 'sv_SE', + 'tr' => 'tr_TR', + 'uk' => 'uk_UA', + 'uz' => 'uz_UZ', + 'vi' => 'vi_VN', + 'zh_CN' => 'zh_CN', + 'zh_TW' => 'zh_TW', + ]; + + /** + * Get the BookStack locale string for the given user. + */ + protected function getLocaleForUser(User $user): string + { + $default = config('app.default_locale'); + + if ($user->isGuest() && config('app.auto_detect_locale')) { + return $this->autoDetectLocale(request(), $default); + } + + return setting()->getUser($user, 'language', $default); + } + + /** + * Get a locale definition for the current user. + */ + public function getForUser(User $user): LocaleDefinition + { + $localeString = $this->getLocaleForUser($user); + + return new LocaleDefinition( + $localeString, + $this->localeMap[$localeString] ?? $localeString, + in_array($localeString, $this->rtlLocales), + ); + } + + /** + * Autodetect the visitors locale by matching locales in their headers + * against the locales supported by BookStack. + */ + protected function autoDetectLocale(Request $request, string $default): string + { + $availableLocales = $this->getAllAppLocales(); + + foreach ($request->getLanguages() as $lang) { + if (in_array($lang, $availableLocales)) { + return $lang; + } + } + + return $default; + } + + /** + * Get all the available app-specific level locale strings. + */ + public function getAllAppLocales(): array + { + return array_keys($this->localeMap); + } +} diff --git a/app/Uploads/HttpFetcher.php b/app/Uploads/HttpFetcher.php deleted file mode 100644 index fcb4147e9..000000000 --- a/app/Uploads/HttpFetcher.php +++ /dev/null @@ -1,38 +0,0 @@ - $uri, - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_CONNECTTIMEOUT => 5, - ]); - - $data = curl_exec($ch); - $err = curl_error($ch); - curl_close($ch); - - if ($err) { - $errno = curl_errno($ch); - throw new HttpFetchException($err, $errno); - } - - return $data; - } -} diff --git a/app/Uploads/UserAvatars.php b/app/Uploads/UserAvatars.php index 3cd37812a..c62324735 100644 --- a/app/Uploads/UserAvatars.php +++ b/app/Uploads/UserAvatars.php @@ -3,20 +3,20 @@ namespace BookStack\Uploads; use BookStack\Exceptions\HttpFetchException; +use BookStack\Http\HttpRequestService; use BookStack\Users\Models\User; use Exception; +use GuzzleHttp\Psr7\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; +use Psr\Http\Client\ClientExceptionInterface; class UserAvatars { - protected $imageService; - protected $http; - - public function __construct(ImageService $imageService, HttpFetcher $http) - { - $this->imageService = $imageService; - $this->http = $http; + public function __construct( + protected ImageService $imageService, + protected HttpRequestService $http + ) { } /** @@ -56,7 +56,7 @@ class UserAvatars /** * Destroy all user avatars uploaded to the given user. */ - public function destroyAllForUser(User $user) + public function destroyAllForUser(User $user): void { $profileImages = Image::query()->where('type', '=', 'user') ->where('uploaded_to', '=', $user->id) @@ -70,7 +70,7 @@ class UserAvatars /** * Save an avatar image from an external service. * - * @throws Exception + * @throws HttpFetchException */ protected function saveAvatarImage(User $user, int $size = 500): Image { @@ -112,28 +112,32 @@ class UserAvatars protected function getAvatarImageData(string $url): string { try { - $imageData = $this->http->fetch($url); - } catch (HttpFetchException $exception) { + $client = $this->http->buildClient(5); + $response = $client->sendRequest(new Request('GET', $url)); + if ($response->getStatusCode() !== 200) { + throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url])); + } + + return (string) $response->getBody(); + } catch (ClientExceptionInterface $exception) { throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]), $exception->getCode(), $exception); } - - return $imageData; } /** * Check if fetching external avatars is enabled. */ - protected function avatarFetchEnabled(): bool + public function avatarFetchEnabled(): bool { $fetchUrl = $this->getAvatarUrl(); - return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0; + return str_starts_with($fetchUrl, 'http'); } /** * Get the URL to fetch avatars from. */ - protected function getAvatarUrl(): string + public function getAvatarUrl(): string { $configOption = config('services.avatar_url'); if ($configOption === false) { diff --git a/app/Users/Controllers/RoleController.php b/app/Users/Controllers/RoleController.php index f6472e4de..0052d829d 100644 --- a/app/Users/Controllers/RoleController.php +++ b/app/Users/Controllers/RoleController.php @@ -13,11 +13,9 @@ use Illuminate\Http\Request; class RoleController extends Controller { - protected PermissionsRepo $permissionsRepo; - - public function __construct(PermissionsRepo $permissionsRepo) - { - $this->permissionsRepo = $permissionsRepo; + public function __construct( + protected PermissionsRepo $permissionsRepo + ) { } /** diff --git a/app/Users/Controllers/UserController.php b/app/Users/Controllers/UserController.php index 1c1b7ba23..0cd48948f 100644 --- a/app/Users/Controllers/UserController.php +++ b/app/Users/Controllers/UserController.php @@ -103,6 +103,7 @@ class UserController extends Controller */ public function edit(int $id, SocialAuthService $socialAuthService) { + $this->preventGuestAccess(); $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); @@ -133,6 +134,7 @@ class UserController extends Controller public function update(Request $request, int $id) { $this->preventAccessInDemoMode(); + $this->preventGuestAccess(); $this->checkPermissionOrCurrentUser('users-manage', $id); $validated = $this->validate($request, [ @@ -176,6 +178,7 @@ class UserController extends Controller */ public function delete(int $id) { + $this->preventGuestAccess(); $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); @@ -192,6 +195,7 @@ class UserController extends Controller public function destroy(Request $request, int $id) { $this->preventAccessInDemoMode(); + $this->preventGuestAccess(); $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); diff --git a/app/Users/Controllers/UserPreferencesController.php b/app/Users/Controllers/UserPreferencesController.php index b20a8aa37..08d65743b 100644 --- a/app/Users/Controllers/UserPreferencesController.php +++ b/app/Users/Controllers/UserPreferencesController.php @@ -3,17 +3,25 @@ namespace BookStack\Users\Controllers; use BookStack\Http\Controller; +use BookStack\Permissions\PermissionApplicator; +use BookStack\Settings\UserNotificationPreferences; use BookStack\Settings\UserShortcutMap; use BookStack\Users\UserRepo; use Illuminate\Http\Request; class UserPreferencesController extends Controller { - protected UserRepo $userRepo; + public function __construct( + protected UserRepo $userRepo + ) { + } - public function __construct(UserRepo $userRepo) + /** + * Show the overview for user preferences. + */ + public function index() { - $this->userRepo = $userRepo; + return view('users.preferences.index'); } /** @@ -24,6 +32,8 @@ class UserPreferencesController extends Controller $shortcuts = UserShortcutMap::fromUserPreferences(); $enabled = setting()->getForCurrentUser('ui-shortcuts-enabled', false); + $this->setPageTitle(trans('preferences.shortcuts_interface')); + return view('users.preferences.shortcuts', [ 'shortcuts' => $shortcuts, 'enabled' => $enabled, @@ -47,6 +57,47 @@ class UserPreferencesController extends Controller return redirect('/preferences/shortcuts'); } + /** + * Show the notification preferences for the current user. + */ + public function showNotifications(PermissionApplicator $permissions) + { + $this->checkPermission('receive-notifications'); + $this->preventGuestAccess(); + + $preferences = (new UserNotificationPreferences(user())); + + $query = user()->watches()->getQuery(); + $query = $permissions->restrictEntityRelationQuery($query, 'watches', 'watchable_id', 'watchable_type'); + $query = $permissions->filterDeletedFromEntityRelationQuery($query, 'watches', 'watchable_id', 'watchable_type'); + $watches = $query->with('watchable')->paginate(20); + + $this->setPageTitle(trans('preferences.notifications')); + return view('users.preferences.notifications', [ + 'preferences' => $preferences, + 'watches' => $watches, + ]); + } + + /** + * Update the notification preferences for the current user. + */ + public function updateNotifications(Request $request) + { + $this->checkPermission('receive-notifications'); + $this->preventGuestAccess(); + $data = $this->validate($request, [ + 'preferences' => ['required', 'array'], + 'preferences.*' => ['required', 'string'], + ]); + + $preferences = (new UserNotificationPreferences(user())); + $preferences->updateFromSettingsArray($data['preferences']); + $this->showSuccessNotification(trans('preferences.notifications_update_success')); + + return redirect('/preferences/notifications'); + } + /** * Update the preferred view format for a list view of the given type. */ @@ -94,7 +145,7 @@ class UserPreferencesController extends Controller */ public function toggleDarkMode() { - $enabled = setting()->getForCurrentUser('dark-mode-enabled', false); + $enabled = setting()->getForCurrentUser('dark-mode-enabled'); setting()->putForCurrentUser('dark-mode-enabled', $enabled ? 'false' : 'true'); return redirect()->back(); @@ -123,7 +174,7 @@ class UserPreferencesController extends Controller { $validated = $this->validate($request, [ 'language' => ['required', 'string', 'max:20'], - 'active' => ['required', 'bool'], + 'active' => ['required', 'bool'], ]); $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', ''); diff --git a/app/Users/Controllers/UserSearchController.php b/app/Users/Controllers/UserSearchController.php index 1c7786f58..b6f37bce0 100644 --- a/app/Users/Controllers/UserSearchController.php +++ b/app/Users/Controllers/UserSearchController.php @@ -14,7 +14,7 @@ class UserSearchController extends Controller */ public function forSelect(Request $request) { - $hasPermission = signedInUser() && ( + $hasPermission = !user()->isGuest() && ( userCan('users-manage') || userCan('restrictions-manage-own') || userCan('restrictions-manage-all') diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index 08cab69fb..39236c7e4 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -3,14 +3,17 @@ namespace BookStack\Users\Models; use BookStack\Access\Mfa\MfaValue; +use BookStack\Access\Notifications\ResetPasswordNotification; use BookStack\Access\SocialAccount; use BookStack\Activity\Models\Favourite; use BookStack\Activity\Models\Loggable; +use BookStack\Activity\Models\Watch; use BookStack\Api\ApiToken; use BookStack\App\Model; use BookStack\App\Sluggable; use BookStack\Entities\Tools\SlugGenerator; -use BookStack\Notifications\ResetPassword; +use BookStack\Translation\LocaleDefinition; +use BookStack\Translation\LocaleManager; use BookStack\Uploads\Image; use Carbon\Carbon; use Exception; @@ -86,35 +89,31 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ protected string $avatarUrl = ''; - /** - * This holds the default user when loaded. - * - * @var null|User - */ - protected static ?User $defaultUser = null; - /** * Returns the default public user. + * Fetches from the container as a singleton to effectively cache at an app level. */ - public static function getDefault(): self + public static function getGuest(): self { - if (!is_null(static::$defaultUser)) { - return static::$defaultUser; - } - - static::$defaultUser = static::query()->where('system_name', '=', 'public')->first(); - - return static::$defaultUser; + return app()->make('users.default'); } /** * Check if the user is the default public user. */ - public function isDefault(): bool + public function isGuest(): bool { return $this->system_name === 'public'; } + /** + * Check if the user has general access to the application. + */ + public function hasAppAccess(): bool + { + return !$this->isGuest() || setting('app-public'); + } + /** * The roles that belong to the user. * @@ -287,6 +286,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $this->hasMany(MfaValue::class); } + /** + * Get the tracked entity watches for this user. + */ + public function watches(): HasMany + { + return $this->hasMany(Watch::class); + } + /** * Get the last activity time for this user. */ @@ -332,7 +339,15 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $splitName[0]; } - return ''; + return mb_substr($this->name, 0, max($chars - 2, 0)) . '…'; + } + + /** + * Get the locale for this user. + */ + public function getLocale(): LocaleDefinition + { + return app()->make(LocaleManager::class)->getForUser($this); } /** @@ -344,7 +359,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function sendPasswordResetNotification($token) { - $this->notify(new ResetPassword($token)); + $this->notify(new ResetPasswordNotification($token)); } /** @@ -360,7 +375,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function refreshSlug(): string { - $this->slug = app(SlugGenerator::class)->generate($this); + $this->slug = app()->make(SlugGenerator::class)->generate($this); return $this->slug; } diff --git a/app/Users/UserRepo.php b/app/Users/UserRepo.php index 408ee6a8e..32e23ecde 100644 --- a/app/Users/UserRepo.php +++ b/app/Users/UserRepo.php @@ -18,18 +18,13 @@ use Illuminate\Support\Str; class UserRepo { - protected UserAvatars $userAvatar; - protected UserInviteService $inviteService; - - /** - * UserRepo constructor. - */ - public function __construct(UserAvatars $userAvatar, UserInviteService $inviteService) - { - $this->userAvatar = $userAvatar; - $this->inviteService = $inviteService; + public function __construct( + protected UserAvatars $userAvatar, + protected UserInviteService $inviteService + ) { } + /** * Get a user by their email address. */ @@ -155,6 +150,7 @@ class UserRepo $user->apiTokens()->delete(); $user->favourites()->delete(); $user->mfaValues()->delete(); + $user->watches()->delete(); $user->delete(); // Delete user profile images diff --git a/app/Util/SsrUrlValidator.php b/app/Util/SsrUrlValidator.php new file mode 100644 index 000000000..0b3a6a31d --- /dev/null +++ b/app/Util/SsrUrlValidator.php @@ -0,0 +1,64 @@ +config = $config ?? config('app.ssr_hosts') ?? ''; + } + + /** + * @throws HttpFetchException + */ + public function ensureAllowed(string $url): void + { + if (!$this->allowed($url)) { + throw new HttpFetchException(trans('errors.http_ssr_url_no_match')); + } + } + + /** + * Check if the given URL is allowed by the configured SSR host values. + */ + public function allowed(string $url): bool + { + $allowed = $this->getHostPatterns(); + + foreach ($allowed as $pattern) { + if ($this->urlMatchesPattern($url, $pattern)) { + return true; + } + } + + return false; + } + + protected function urlMatchesPattern($url, $pattern): bool + { + $pattern = rtrim(trim($pattern), '/'); + $url = trim($url); + + if (empty($pattern) || empty($url)) { + return false; + } + + $quoted = preg_quote($pattern, '/'); + $regexPattern = str_replace('\*', '.*', $quoted); + + return preg_match('/^' . $regexPattern . '($|\/.*$|#.*$)/i', $url); + } + + /** + * @return string[] + */ + protected function getHostPatterns(): array + { + return explode(' ', strtolower($this->config)); + } +} diff --git a/app/Util/SvgIcon.php b/app/Util/SvgIcon.php new file mode 100644 index 000000000..ce6e1c23e --- /dev/null +++ b/app/Util/SvgIcon.php @@ -0,0 +1,39 @@ + 'svg-icon', + 'data-icon' => $this->name, + 'role' => 'presentation', + ], $this->attrs); + + $attrString = ' '; + foreach ($attrs as $attrName => $attr) { + $attrString .= $attrName . '="' . $attr . '" '; + } + + $iconPath = resource_path('icons/' . $this->name . '.svg'); + $themeIconPath = theme_path('icons/' . $this->name . '.svg'); + + if ($themeIconPath && file_exists($themeIconPath)) { + $iconPath = $themeIconPath; + } elseif (!file_exists($iconPath)) { + return ''; + } + + $fileContents = file_get_contents($iconPath); + + return str_replace('=5.5" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -98,9 +99,8 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", - "psr/http-message": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" @@ -151,9 +151,9 @@ "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.269.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.3" }, - "time": "2023-04-26T18:21:04+00:00" + "time": "2023-09-08T18:06:26+00:00" }, { "name": "bacon/bacon-qr-code", @@ -421,16 +421,16 @@ }, { "name": "dasprid/enum", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", "shasum": "" }, "require": { @@ -465,9 +465,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.4" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" }, - "time": "2023-03-01T18:44:03+00:00" + "time": "2023-08-25T16:18:39+00:00" }, { "name": "dflydev/dot-access-data", @@ -639,16 +639,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.4", + "version": "3.6.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", + "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", "shasum": "" }, "require": { @@ -663,11 +663,12 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.14", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.29", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.7", + "phpunit/phpunit": "9.6.9", "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", @@ -731,7 +732,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.4" + "source": "https://github.com/doctrine/dbal/tree/3.6.6" }, "funding": [ { @@ -747,7 +748,7 @@ "type": "tidelift" } ], - "time": "2023-06-15T07:40:12+00:00" + "time": "2023-08-17T05:38:17+00:00" }, { "name": "doctrine/deprecations", @@ -1121,16 +1122,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -1170,7 +1171,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -1178,7 +1179,7 @@ "type": "github" } ], - "time": "2022-09-10T18:51:20+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "egulias/email-validator", @@ -1382,22 +1383,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1488,7 +1489,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.0" }, "funding": [ { @@ -1504,33 +1505,37 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2023-08-27T10:20:53+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1567,7 +1572,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.3" + "source": "https://github.com/guzzle/promises/tree/2.0.1" }, "funding": [ { @@ -1583,20 +1588,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T12:31:43+00:00" + "time": "2023-08-03T15:11:55+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "shasum": "" }, "require": { @@ -1683,7 +1688,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.1" }, "funding": [ { @@ -1699,20 +1704,20 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-08-27T10:13:57+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2" + "reference": "61bf437fc2197f587f6857d3ff903a24f1731b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/b945d74a55a25a949158444f09ec0d3c120d69e2", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/61bf437fc2197f587f6857d3ff903a24f1731b5d", + "reference": "61bf437fc2197f587f6857d3ff903a24f1731b5d", "shasum": "" }, "require": { @@ -1720,15 +1725,11 @@ "symfony/polyfill-php80": "^1.17" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", "phpunit/phpunit": "^8.5.19 || ^9.5.8", "uri-template/tests": "1.0.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { "GuzzleHttp\\UriTemplate\\": "src" @@ -1767,7 +1768,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.1" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.2" }, "funding": [ { @@ -1783,7 +1784,7 @@ "type": "tidelift" } ], - "time": "2021-10-07T12:57:01+00:00" + "time": "2023-08-27T10:19:19+00:00" }, { "name": "intervention/image", @@ -1871,16 +1872,16 @@ }, { "name": "knplabs/knp-snappy", - "version": "v1.4.2", + "version": "v1.4.3", "source": { "type": "git", "url": "https://github.com/KnpLabs/snappy.git", - "reference": "b66f79334421c26d9c244427963fa2d92980b5d3" + "reference": "d3b742d61a68bf93866032c2c0a7f1486128b67e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/b66f79334421c26d9c244427963fa2d92980b5d3", - "reference": "b66f79334421c26d9c244427963fa2d92980b5d3", + "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/d3b742d61a68bf93866032c2c0a7f1486128b67e", + "reference": "d3b742d61a68bf93866032c2c0a7f1486128b67e", "shasum": "" }, "require": { @@ -1891,8 +1892,8 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.16||^3.0", "pedrotroller/php-cs-custom-fixer": "^2.19", - "phpstan/phpstan": "^0.12.7", - "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.0.0", "phpunit/phpunit": "~7.4||~8.5" }, "suggest": { @@ -1939,22 +1940,22 @@ ], "support": { "issues": "https://github.com/KnpLabs/snappy/issues", - "source": "https://github.com/KnpLabs/snappy/tree/v1.4.2" + "source": "https://github.com/KnpLabs/snappy/tree/v1.4.3" }, - "time": "2023-03-17T14:47:54+00:00" + "time": "2023-09-06T15:24:48+00:00" }, { "name": "laravel/framework", - "version": "v9.52.10", + "version": "v9.52.15", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "858add225ce88a76c43aec0e7866288321ee0ee9" + "reference": "e3350e87a52346af9cc655a3012d2175d2d05ad7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9", - "reference": "858add225ce88a76c43aec0e7866288321ee0ee9", + "url": "https://api.github.com/repos/laravel/framework/zipball/e3350e87a52346af9cc655a3012d2175d2d05ad7", + "reference": "e3350e87a52346af9cc655a3012d2175d2d05ad7", "shasum": "" }, "require": { @@ -2139,20 +2140,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-06-27T13:25:54+00:00" + "time": "2023-08-08T14:28:40+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902", + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902", "shasum": "" }, "require": { @@ -2199,20 +2200,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-01-30T18:31:20+00:00" + "time": "2023-07-14T13:56:28+00:00" }, { "name": "laravel/socialite", - "version": "v5.6.3", + "version": "v5.9.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "00ea7f8630673ea49304fc8a9fca5a64eb838c7e" + "reference": "14acfa3262875f180fba51efe3c7aaa089a9ef24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/00ea7f8630673ea49304fc8a9fca5a64eb838c7e", - "reference": "00ea7f8630673ea49304fc8a9fca5a64eb838c7e", + "url": "https://api.github.com/repos/laravel/socialite/zipball/14acfa3262875f180fba51efe3c7aaa089a9ef24", + "reference": "14acfa3262875f180fba51efe3c7aaa089a9ef24", "shasum": "" }, "require": { @@ -2269,20 +2270,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-06-06T13:42:43+00:00" + "time": "2023-09-05T15:20:21+00:00" }, { "name": "laravel/tinker", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10" + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", + "url": "https://api.github.com/repos/laravel/tinker/zipball/b936d415b252b499e8c3b1f795cd4fc20f57e1f3", + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3", "shasum": "" }, "require": { @@ -2295,6 +2296,7 @@ }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { @@ -2335,22 +2337,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.1" + "source": "https://github.com/laravel/tinker/tree/v2.8.2" }, - "time": "2023-02-15T16:40:09+00:00" + "time": "2023-08-15T14:27:00+00:00" }, { "name": "league/commonmark", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", "shasum": "" }, "require": { @@ -2443,7 +2445,7 @@ "type": "tidelift" } ], - "time": "2023-03-24T15:16:10+00:00" + "time": "2023-08-30T16:55:00+00:00" }, { "name": "league/config", @@ -2529,16 +2531,16 @@ }, { "name": "league/flysystem", - "version": "3.15.1", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed" + "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a141d430414fcb8bf797a18716b09f759a385bed", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729", + "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729", "shasum": "" }, "require": { @@ -2547,6 +2549,8 @@ "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -2566,7 +2570,7 @@ "microsoft/azure-storage-blob": "^1.1", "phpseclib/phpseclib": "^3.0.14", "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", + "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, "type": "library", @@ -2601,7 +2605,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.15.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.16.0" }, "funding": [ { @@ -2613,20 +2617,20 @@ "type": "github" } ], - "time": "2023-05-04T09:04:26+00:00" + "time": "2023-09-07T19:22:17+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.15.0", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a" + "reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d8de61ee10b6a607e7996cff388c5a3a663e8c8a", - "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c", + "reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c", "shasum": "" }, "require": { @@ -2667,7 +2671,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.15.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.16.0" }, "funding": [ { @@ -2679,20 +2683,20 @@ "type": "github" } ], - "time": "2023-05-02T20:02:14+00:00" + "time": "2023-08-30T10:14:57+00:00" }, { "name": "league/flysystem-local", - "version": "3.15.0", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3" + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/543f64c397fefdf9cfeac443ffb6beff602796b3", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ec7383f25642e6fd4bb0c9554fc2311245391781", + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781", "shasum": "" }, "require": { @@ -2727,7 +2731,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.15.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.16.0" }, "funding": [ { @@ -2739,20 +2743,20 @@ "type": "github" } ], - "time": "2023-05-02T20:02:14+00:00" + "time": "2023-08-30T10:23:59+00:00" }, { "name": "league/html-to-markdown", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/thephpleague/html-to-markdown.git", - "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1" + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", - "reference": "e0fc8cf07bdabbcd3765341ecb50c34c271d64e1", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd", + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd", "shasum": "" }, "require": { @@ -2762,11 +2766,11 @@ }, "require-dev": { "mikehaertl/php-shellcommand": "^1.1.0", - "phpstan/phpstan": "^0.12.99", + "phpstan/phpstan": "^1.8.8", "phpunit/phpunit": "^8.5 || ^9.2", "scrutinizer/ocular": "^1.6", - "unleashedtech/php-coding-standard": "^2.7", - "vimeo/psalm": "^4.22" + "unleashedtech/php-coding-standard": "^2.7 || ^3.0", + "vimeo/psalm": "^4.22 || ^5.0" }, "bin": [ "bin/html-to-markdown" @@ -2808,7 +2812,7 @@ ], "support": { "issues": "https://github.com/thephpleague/html-to-markdown/issues", - "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.0" + "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1" }, "funding": [ { @@ -2828,30 +2832,30 @@ "type": "tidelift" } ], - "time": "2022-03-02T17:24:08+00:00" + "time": "2023-07-12T21:21:09+00:00" }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -2872,7 +2876,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" }, "funding": [ { @@ -2884,7 +2888,7 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2023-08-05T12:09:49+00:00" }, { "name": "league/oauth1-client", @@ -3034,16 +3038,16 @@ }, { "name": "masterminds/html5", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3" + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", - "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", + "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", "shasum": "" }, "require": { @@ -3095,9 +3099,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" }, - "time": "2023-04-26T07:27:39+00:00" + "time": "2023-05-10T11:58:31+00:00" }, { "name": "monolog/monolog", @@ -3203,25 +3207,25 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -3229,7 +3233,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -3245,6 +3249,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -3258,31 +3267,35 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "nesbot/carbon", - "version": "2.68.1", + "version": "2.70.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da" + "reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d3298b38ea8612e5f77d38d1a99438e42f70341d", + "reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16", "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, + "provide": { + "psr/clock-implementation": "1.0" + }, "require-dev": { "doctrine/dbal": "^2.0 || ^3.1.4", "doctrine/orm": "^2.7", @@ -3362,25 +3375,25 @@ "type": "tidelift" } ], - "time": "2023-06-20T18:29:04+00:00" + "time": "2023-09-07T16:43:50+00:00" }, { "name": "nette/schema", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", "shasum": "" }, "require": { "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.3" + "php": "7.1 - 8.3" }, "require-dev": { "nette/tester": "^2.3 || ^2.4", @@ -3422,26 +3435,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.3" + "source": "https://github.com/nette/schema/tree/v1.2.4" }, - "time": "2022-10-13T01:24:26+00:00" + "time": "2023-08-05T18:56:25+00:00" }, { "name": "nette/utils", - "version": "v4.0.0", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "url": "https://api.github.com/repos/nette/utils/zipball/9124157137da01b1f5a5a22d6486cb975f26db7e", + "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e", "shasum": "" }, "require": { - "php": ">=8.0 <8.3" + "php": ">=8.0 <8.4" }, "conflict": { "nette/finder": "<3", @@ -3449,7 +3462,7 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "^2.4", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.9" }, @@ -3509,22 +3522,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.0" + "source": "https://github.com/nette/utils/tree/v4.0.1" }, - "time": "2023-02-02T10:41:53+00:00" + "time": "2023-07-30T15:42:21+00:00" }, { "name": "nikic/php-parser", - "version": "v4.16.0", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -3565,9 +3578,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-06-25T14:52:30+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "nunomaduro/termwind", @@ -3995,16 +4008,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.20", + "version": "3.0.21", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67" + "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67", - "reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4580645d3fc05c189024eb3b834c6c1e4f0f30a1", + "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1", "shasum": "" }, "require": { @@ -4085,7 +4098,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.20" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.21" }, "funding": [ { @@ -4101,7 +4114,7 @@ "type": "tidelift" } ], - "time": "2023-06-13T06:30:34+00:00" + "time": "2023-07-09T15:24:48+00:00" }, { "name": "pragmarx/google2fa", @@ -4157,16 +4170,16 @@ }, { "name": "predis/predis", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "33b70b971a32b0d28b4f748b0547593dce316e0d" + "reference": "5f2b410a74afaff296a87a494e4c5488cf9fab57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/33b70b971a32b0d28b4f748b0547593dce316e0d", - "reference": "33b70b971a32b0d28b4f748b0547593dce316e0d", + "url": "https://api.github.com/repos/predis/predis/zipball/5f2b410a74afaff296a87a494e4c5488cf9fab57", + "reference": "5f2b410a74afaff296a87a494e4c5488cf9fab57", "shasum": "" }, "require": { @@ -4206,7 +4219,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.2.0" + "source": "https://github.com/predis/predis/tree/v2.2.1" }, "funding": [ { @@ -4214,7 +4227,7 @@ "type": "github" } ], - "time": "2023-06-14T10:37:31+00:00" + "time": "2023-08-15T23:01:46+00:00" }, { "name": "psr/cache", @@ -4265,6 +4278,54 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -4631,16 +4692,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.18", + "version": "v0.11.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec" + "reference": "0fa27040553d1d280a67a4393194df5228afea5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0fa27040553d1d280a67a4393194df5228afea5b", + "reference": "0fa27040553d1d280a67a4393194df5228afea5b", "shasum": "" }, "require": { @@ -4701,9 +4762,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.18" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.20" }, - "time": "2023-05-23T02:31:11+00:00" + "time": "2023-07-31T14:32:22+00:00" }, { "name": "ralouphie/getallheaders", @@ -5028,16 +5089,16 @@ }, { "name": "socialiteproviders/discord", - "version": "4.1.2", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Discord.git", - "reference": "11f6a8ded5b1948723886f2e5413b91139fcce6b" + "reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/11f6a8ded5b1948723886f2e5413b91139fcce6b", - "reference": "11f6a8ded5b1948723886f2e5413b91139fcce6b", + "url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/c71c379acfdca5ba4aa65a3db5ae5222852a919c", + "reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c", "shasum": "" }, "require": { @@ -5074,7 +5135,7 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2023-02-01T08:54:49+00:00" + "time": "2023-07-24T23:28:47+00:00" }, { "name": "socialiteproviders/gitlab", @@ -5119,22 +5180,22 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.3.0", + "version": "v4.4.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c" + "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/47402cbc5b7ef445317e799bf12fd5a12062206c", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/df5e45b53d918ec3d689f014d98a6c838b98ed96", + "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96", "shasum": "" }, "require": { "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0", "laravel/socialite": "~5.0", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.2", @@ -5189,7 +5250,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2023-01-26T23:11:27+00:00" + "time": "2023-08-27T23:46:34+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -5244,21 +5305,21 @@ }, { "name": "socialiteproviders/okta", - "version": "4.2.1", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Okta.git", - "reference": "7c0b7522423943131f680e74123b71ccd3989541" + "reference": "e5fb62035bfa0ccdbc8facf4cf205428fc502edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/7c0b7522423943131f680e74123b71ccd3989541", - "reference": "7c0b7522423943131f680e74123b71ccd3989541", + "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/e5fb62035bfa0ccdbc8facf4cf205428fc502edb", + "reference": "e5fb62035bfa0ccdbc8facf4cf205428fc502edb", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "socialiteproviders/manager": "~4.0" }, "type": "library", @@ -5290,48 +5351,7 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2022-03-14T23:25:14+00:00" - }, - { - "name": "socialiteproviders/slack", - "version": "4.1.1", - "source": { - "type": "git", - "url": "https://github.com/SocialiteProviders/Slack.git", - "reference": "2b781c95daf06ec87a8f3deba2ab613d6bea5e8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Slack/zipball/2b781c95daf06ec87a8f3deba2ab613d6bea5e8d", - "reference": "2b781c95daf06ec87a8f3deba2ab613d6bea5e8d", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.2 || ^8.0", - "socialiteproviders/manager": "~4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "SocialiteProviders\\Slack\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Faust", - "email": "hello@brianfaust.de" - } - ], - "description": "Slack OAuth2 Provider for Laravel Socialite", - "support": { - "source": "https://github.com/SocialiteProviders/Slack/tree/4.1.1" - }, - "time": "2021-03-26T04:10:10+00:00" + "time": "2022-09-06T03:39:26+00:00" }, { "name": "socialiteproviders/twitch", @@ -6284,16 +6304,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -6308,7 +6328,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6346,7 +6366,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -6362,20 +6382,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -6387,7 +6407,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6427,7 +6447,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -6443,20 +6463,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -6470,7 +6490,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6514,7 +6534,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -6530,20 +6550,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -6555,7 +6575,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6598,7 +6618,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -6614,20 +6634,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -6642,7 +6662,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6681,7 +6701,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -6697,20 +6717,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -6719,7 +6739,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6757,7 +6777,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -6773,20 +6793,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -6795,7 +6815,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6840,7 +6860,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -6856,20 +6876,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", "shasum": "" }, "require": { @@ -6878,7 +6898,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6919,7 +6939,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" }, "funding": [ { @@ -6935,20 +6955,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", "shasum": "" }, "require": { @@ -6963,7 +6983,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -7001,7 +7021,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" }, "funding": [ { @@ -7017,7 +7037,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", @@ -8081,16 +8101,16 @@ }, { "name": "filp/whoops", - "version": "2.15.2", + "version": "2.15.3", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73" + "reference": "c83e88a30524f9360b11f585f71e6b17313b7187" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", - "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187", + "reference": "c83e88a30524f9360b11f585f71e6b17313b7187", "shasum": "" }, "require": { @@ -8140,7 +8160,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.2" + "source": "https://github.com/filp/whoops/tree/2.15.3" }, "funding": [ { @@ -8148,7 +8168,7 @@ "type": "github" } ], - "time": "2023-04-12T12:00:00+00:00" + "time": "2023-07-13T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8271,37 +8291,33 @@ }, { "name": "mockery/mockery", - "version": "1.6.2", + "version": "1.6.6", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191" + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191", + "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.4 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3", - "psalm/plugin-phpunit": "^0.18", - "vimeo/psalm": "^5.9" + "phpunit/phpunit": "^8.5 || ^9.6.10", + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^4.30" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.6.x-dev" - } - }, "autoload": { "files": [ "library/helpers.php", @@ -8319,12 +8335,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -8342,10 +8366,13 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.6.2" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2023-06-07T09:07:52+00:00" + "time": "2023-08-09T00:03:52+00:00" }, { "name": "myclabs/deep-copy", @@ -8496,16 +8523,16 @@ }, { "name": "nunomaduro/larastan", - "version": "v2.6.3", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "73e5be5f5c732212ce6ca77ffd2753a136f36a23" + "reference": "6c5e8820f3db6397546f3ce48520af9d312aed27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/73e5be5f5c732212ce6ca77ffd2753a136f36a23", - "reference": "73e5be5f5c732212ce6ca77ffd2753a136f36a23", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/6c5e8820f3db6397546f3ce48520af9d312aed27", + "reference": "6c5e8820f3db6397546f3ce48520af9d312aed27", "shasum": "" }, "require": { @@ -8568,7 +8595,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/v2.6.3" + "source": "https://github.com/nunomaduro/larastan/tree/v2.6.4" }, "funding": [ { @@ -8588,7 +8615,7 @@ "type": "patreon" } ], - "time": "2023-06-13T21:39:27+00:00" + "time": "2023-07-29T12:13:13+00:00" }, { "name": "phar-io/manifest", @@ -8790,16 +8817,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.23", + "version": "1.10.33", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "65ab678d1248a8bc6fde456f0d7ff3562a61a4cd" + "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/65ab678d1248a8bc6fde456f0d7ff3562a61a4cd", - "reference": "65ab678d1248a8bc6fde456f0d7ff3562a61a4cd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", + "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", "shasum": "" }, "require": { @@ -8848,20 +8875,20 @@ "type": "tidelift" } ], - "time": "2023-07-04T13:32:44+00:00" + "time": "2023-09-04T12:20:53+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -8917,7 +8944,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -8925,7 +8953,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9170,16 +9198,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.9", + "version": "9.6.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a9aceaf20a682aeacf28d582654a1670d8826778" + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778", - "reference": "a9aceaf20a682aeacf28d582654a1670d8826778", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", "shasum": "" }, "require": { @@ -9253,7 +9281,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" }, "funding": [ { @@ -9269,7 +9297,7 @@ "type": "tidelift" } ], - "time": "2023-06-11T06:13:56+00:00" + "time": "2023-08-19T07:10:56+00:00" }, { "name": "sebastian/cli-parser", @@ -9777,16 +9805,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -9829,7 +9857,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -9837,7 +9865,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", diff --git a/database/migrations/2023_07_25_124945_add_receive_notifications_role_permissions.php b/database/migrations/2023_07_25_124945_add_receive_notifications_role_permissions.php new file mode 100644 index 000000000..4872e421e --- /dev/null +++ b/database/migrations/2023_07_25_124945_add_receive_notifications_role_permissions.php @@ -0,0 +1,51 @@ +insertGetId([ + 'name' => 'receive-notifications', + 'display_name' => 'Receive & Manage Notifications', + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); + + $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id; + DB::table('permission_role')->insert([ + 'role_id' => $adminRoleId, + 'permission_id' => $permissionId, + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $permission = DB::table('role_permissions') + ->where('name', '=', 'receive-notifications') + ->first(); + + if ($permission) { + DB::table('permission_role')->where([ + 'permission_id' => $permission->id, + ])->delete(); + } + + DB::table('role_permissions') + ->where('name', '=', 'receive-notifications') + ->delete(); + } +}; diff --git a/database/migrations/2023_07_31_104430_create_watches_table.php b/database/migrations/2023_07_31_104430_create_watches_table.php new file mode 100644 index 000000000..e2a5c20d0 --- /dev/null +++ b/database/migrations/2023_07_31_104430_create_watches_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('user_id')->index(); + $table->integer('watchable_id'); + $table->string('watchable_type', 100); + $table->tinyInteger('level', false, true)->index(); + $table->timestamps(); + + $table->index(['watchable_id', 'watchable_type'], 'watchable_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('watches'); + } +}; diff --git a/database/migrations/2023_08_21_174248_increase_cache_size.php b/database/migrations/2023_08_21_174248_increase_cache_size.php new file mode 100644 index 000000000..865472c2e --- /dev/null +++ b/database/migrations/2023_08_21_174248_increase_cache_size.php @@ -0,0 +1,32 @@ +mediumText('value')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('cache', function (Blueprint $table) { + $table->text('value')->change(); + }); + } +}; diff --git a/database/seeders/DummyContentSeeder.php b/database/seeders/DummyContentSeeder.php index 0f030d671..47e8d1d7c 100644 --- a/database/seeders/DummyContentSeeder.php +++ b/database/seeders/DummyContentSeeder.php @@ -27,6 +27,8 @@ class DummyContentSeeder extends Seeder // Create an editor user $editorUser = User::factory()->create(); $editorRole = Role::getRole('editor'); + $additionalEditorPerms = ['receive-notifications', 'comment-create-all']; + $editorRole->permissions()->syncWithoutDetaching(RolePermission::whereIn('name', $additionalEditorPerms)->pluck('id')); $editorUser->attachRole($editorRole); // Create a viewer user diff --git a/dev/api/requests/chapters-create.json b/dev/api/requests/chapters-create.json index ca06fc298..a7a0e072c 100644 --- a/dev/api/requests/chapters-create.json +++ b/dev/api/requests/chapters-create.json @@ -2,8 +2,9 @@ "book_id": 1, "name": "My fantastic new chapter", "description": "This is a great new chapter that I've created via the API", + "priority": 15, "tags": [ {"name": "Category", "value": "Top Content"}, {"name": "Rating", "value": "Highest"} ] -} \ No newline at end of file +} diff --git a/dev/api/requests/chapters-update.json b/dev/api/requests/chapters-update.json index 6bd3a3e5c..18c40301b 100644 --- a/dev/api/requests/chapters-update.json +++ b/dev/api/requests/chapters-update.json @@ -2,8 +2,9 @@ "book_id": 1, "name": "My fantastic updated chapter", "description": "This is an updated chapter that I've altered via the API", + "priority": 16, "tags": [ {"name": "Category", "value": "Kinda Good Content"}, {"name": "Rating", "value": "Medium"} ] -} \ No newline at end of file +} diff --git a/dev/api/requests/pages-create.json b/dev/api/requests/pages-create.json index 1f53b42d4..bb32943a2 100644 --- a/dev/api/requests/pages-create.json +++ b/dev/api/requests/pages-create.json @@ -2,8 +2,9 @@ "book_id": 1, "name": "My API Page", "html": "

my new API page

", + "priority": 15, "tags": [ {"name": "Category", "value": "Not Bad Content"}, {"name": "Rating", "value": "Average"} ] -} \ No newline at end of file +} diff --git a/dev/api/requests/pages-update.json b/dev/api/requests/pages-update.json index b9bfeb630..e3ca9004e 100644 --- a/dev/api/requests/pages-update.json +++ b/dev/api/requests/pages-update.json @@ -2,8 +2,9 @@ "chapter_id": 1, "name": "My updated API Page", "html": "

my new API page - Updated

", + "priority": 16, "tags": [ {"name": "Category", "value": "API Examples"}, {"name": "Rating", "value": "Alright"} ] -} \ No newline at end of file +} diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json index 4dbc764b1..cf47b123d 100644 --- a/dev/api/responses/chapters-create.json +++ b/dev/api/responses/chapters-create.json @@ -4,7 +4,7 @@ "slug": "my-fantastic-new-chapter", "name": "My fantastic new chapter", "description": "This is a great new chapter that I've created via the API", - "priority": 6, + "priority": 15, "created_by": 1, "updated_by": 1, "owned_by": 1, diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json index cc454d740..a4940af2d 100644 --- a/dev/api/responses/chapters-update.json +++ b/dev/api/responses/chapters-update.json @@ -4,7 +4,7 @@ "slug": "my-fantastic-updated-chapter", "name": "My fantastic updated chapter", "description": "This is an updated chapter that I've altered via the API", - "priority": 7, + "priority": 16, "created_at": "2020-05-22T23:03:35.000000Z", "updated_at": "2020-05-22T23:07:20.000000Z", "created_by": 1, diff --git a/dev/api/responses/pages-create.json b/dev/api/responses/pages-create.json index 385d5384e..11f5ab8c8 100644 --- a/dev/api/responses/pages-create.json +++ b/dev/api/responses/pages-create.json @@ -6,7 +6,7 @@ "slug": "my-api-page", "html": "

my new API page

", "raw_html": "

my new API page

", - "priority": 14, + "priority": 15, "created_at": "2020-11-28T15:01:39.000000Z", "updated_at": "2020-11-28T15:01:39.000000Z", "created_by": { diff --git a/dev/docs/development.md b/dev/docs/development.md index a68ae50b4..3c7a6e9d2 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -3,7 +3,7 @@ All development on BookStack is currently done on the `development` branch. When it's time for a release the `development` branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements: -* [Node.js](https://nodejs.org/en/) v16.0+ +* [Node.js](https://nodejs.org/en/) v18.0+ ## Building CSS & JavaScript Assets @@ -23,13 +23,13 @@ npm run production npm run dev ``` -BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, username and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing. +Further details about the BookStack JavaScript codebase can be found in the [javascript-code.md document](javascript-code.md). -The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`. +## Automated App Testing -Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`. +BookStack has a large suite of PHP tests to cover application functionality. We try to ensure that all additions and changes to the platform are covered with testing. -If the codebase needs to be tested with deprecations, this can be done via uncommenting the relevant line within the TestCase@setUp function. +For details about setting-up, running and writing tests please see the [php-testing.md document](php-testing.md). ## Code Standards diff --git a/dev/docs/javascript-code.md b/dev/docs/javascript-code.md index 9820839a4..ba7d79972 100644 --- a/dev/docs/javascript-code.md +++ b/dev/docs/javascript-code.md @@ -85,18 +85,18 @@ Will result with `this.$opts` being: A component has the below shown properties & methods available for use. As mentioned above, most of these should be used within the `setup()` function to make the requirements/dependencies of the component clear. ```javascript -// The root element that the compontent has been applied to. +// The root element that the component has been applied to. this.$el -// A map of defined element references within the compontent. +// A map of defined element references within the component. // See "Element References" above. this.$refs -// A map of defined multi-element references within the compontent. +// A map of defined multi-element references within the component. // See "Element References" above. this.$manyRefs -// Options defined for the compontent. +// Options defined for the component. this.$opts // The registered name of the component, usually kebab-case. @@ -160,4 +160,4 @@ window.$components.firstOnElement(element, name); There are a range of available events that are emitted as part of a public & supported API for accessing or extending JavaScript libraries & components used in the system. -Details on these events can be found in the [JavaScript Public Events file](javascript-public-events.md). \ No newline at end of file +Details on these events can be found in the [JavaScript Public Events file](javascript-public-events.md). diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md index 1e95dc8c5..95300ddd3 100644 --- a/dev/docs/javascript-public-events.md +++ b/dev/docs/javascript-public-events.md @@ -42,7 +42,7 @@ This event is called before the markdown input editor CodeMirror instance is cre #### Event Data -- `editorViewConfig` - An [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) object that will eventially be passed when creating the CodeMirror EditorView instance. +- `editorViewConfig` - An [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) object that will eventually be passed when creating the CodeMirror EditorView instance. ##### Example @@ -252,4 +252,4 @@ window.addEventListener('library-cm6::configure-theme', event => { detail.registerHighlightStyle(highlightStyleBuilder); }); ``` - \ No newline at end of file + diff --git a/dev/docs/php-testing.md b/dev/docs/php-testing.md new file mode 100644 index 000000000..1bd4a414f --- /dev/null +++ b/dev/docs/php-testing.md @@ -0,0 +1,87 @@ +# BookStack PHP Testing + +BookStack has many test cases defined within the `tests/` directory of the app. These are built upon [PHPUnit](https://phpunit.de/) along with Laravel's own test framework additions, and a bunch of custom helper classes. + +## Setup + +The application tests are mostly functional, rather than unit tests, meaning they simulate user actions and system components and therefore these require use of the database. To avoid potential conflicts within your development environment, the tests use a separate database. This is defined via a specific `mysql_testing` database connection in our configuration, and expects to use the following database access details: + +- Host: `127.0.0.1` +- Username: `bookstack-test` +- Password: `bookstack-test` +- Database: `bookstack-test` + +You will need to create a database, with access for these credentials, to allow the system to connect when running tests. Alternatively, if those don't suit, you can define a `TEST_DATABASE_URL` option in your `.env` file, or environment, with connection details like so: + +```bash +TEST_DATABASE_URL="mysql://username:password@host-name:port/database-name" +``` + +The testing database will need migrating and seeding with test data beforehand. This can be done by running `composer refresh-test-database`. + +## Running Tests + +You can run all tests via composer with `composer test` in the application root directory. +Alternatively, you can run PHPUnit directly with `php vendor/bin/phpunit`. + +Some editors, like PHPStorm, have in-built support for running tests on a per file, directory or class basis. +Otherwise, you can run PHPUnit with specified tests and/or filter to limit the tests ran: + +```bash +# Run all test in the "./tests/HomepageTest.php" file +php vendor/bin/phpunit ./tests/HomepageTest.php + +# Run all test in the "./tests/User" directory +php vendor/bin/phpunit ./tests/User + +# Filter to a particular test method name +php vendor/bin/phpunit --filter test_default_homepage_visible + +# Filter to a particular test class name +php vendor/bin/phpunit --filter HomepageTest +``` + +If the codebase needs to be tested with deprecations, this can be done via uncommenting the relevant line within the `TestCase@setUp` function. This is not expected for most PRs to the project, but instead used for maintenance tasks like dependency & PHP upgrades. + +## Writing Tests + +To understand how tests are written & used, it's advised you read through existing test cases similar to what you need to write. Tests are written in a rather scrappy manner, compared to the core app codebase, which is fine and expected since there's often hoops to jump through for various functionality. Scrappy tests are better than no tests. + +Test classes have to be within the `tests/` folder, and be named ending in `Test`. These should always extend the `Tests\TestCase` class. +Test methods should be written in snake_case, start with `test_`, and be public methods. + +Here are some general rules & patterns we follow in the tests: + +- All external remote system resources, like HTTP calls and LDAP connections, are mocked. +- We prefer to hard-code expected text & URLs to better detect potential changes in the system rather than use dynamic references. This provides higher sensitivity to changes, and has never been much of a maintenance issue. +- Only test with an admin user if needed, otherwise keep to less privileged users to ensure permission systems are active and exercised within tests. +- If testing for the lack of something (e.g. `$this->assertDontSee('TextAfterChange')`) then this should be accompanied by some form of positive confirmation (e.g. `$this->assertSee('TextBeforeChange')`). + +### Test Helpers + +Our default `TestCase` is bloated with helpers to assist in testing scenarios. Some of these shown below, but you should jump through and explore these in your IDE/editor to explore their full capabilities and options: + +```php +// Run the test as a logged-in-user at a certain privilege level +$this->asAdmin(); +$this->asEditor(); +$this->asViewer(); + +// Provides a bunch of entity (shelf/book/chapter/page) content and actions +$this->entities; + +// Provides various user & role abilities +$this->users; + +// Provides many helpful actions relate to system & content permissions +$this->permissions; + +// Provides a range of methods for dealing with files & uploads in tests +$this->files; + +// Parse HTML of a response to assert HTML-based conditions +// Uses https://github.com/ssddanbrown/asserthtml library. +$this->withHtml($resp); +// Example: +$this->withHtml($this->get('/'))->assertElementContains('p[id="top"]', 'Hello!'); +``` \ No newline at end of file diff --git a/lang/ar/activities.php b/lang/ar/activities.php index cc7a159c6..f4065b1f1 100644 --- a/lang/ar/activities.php +++ b/lang/ar/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => 'تم إضافة ":name" إلى المفضلة لديك', 'favourite_remove_notification' => 'تم إزالة ":name" من المفضلة لديك', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'تم التعليق', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'تحديث الأذونات', ]; diff --git a/lang/ar/common.php b/lang/ar/common.php index d85c76ed2..fa838a810 100644 --- a/lang/ar/common.php +++ b/lang/ar/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'إزالة', 'add' => 'إضافة', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'شاشة كاملة', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/ar/entities.php b/lang/ar/entities.php index b08dae623..fc544aa0d 100644 --- a/lang/ar/entities.php +++ b/lang/ar/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'إدخال رسمة', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'صفحة ليست في فصل', 'pages_move' => 'نقل الصفحة', 'pages_copy' => 'نسخ الصفحة', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/ar/errors.php b/lang/ar/errors.php index cf50db0e3..e6561e8bd 100644 --- a/lang/ar/errors.php +++ b/lang/ar/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'حدث خطأ عند إرسال بريد إلكتروني تجريبي:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ar/notifications.php b/lang/ar/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/ar/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/ar/preferences.php b/lang/ar/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/ar/preferences.php +++ b/lang/ar/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/ar/settings.php b/lang/ar/settings.php index f0efc18f0..8023e0110 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'إدارة إعدادات التطبيق', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'أذونات الأصول', 'roles_system_warning' => 'اعلم أن الوصول إلى أي من الأذونات الثلاثة المذكورة أعلاه يمكن أن يسمح للمستخدم بتغيير امتيازاته الخاصة أو امتيازات الآخرين في النظام. قم بتعيين الأدوار مع هذه الأذونات فقط للمستخدمين الموثوق بهم.', 'role_asset_desc' => 'تتحكم هذه الأذونات في الوصول الافتراضي إلى الأصول داخل النظام. ستتجاوز الأذونات الخاصة بالكتب والفصول والصفحات هذه الأذونات.', diff --git a/lang/bg/activities.php b/lang/bg/activities.php index 0bfbfdf95..2c58883aa 100644 --- a/lang/bg/activities.php +++ b/lang/bg/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" е добавен към любими успешно', 'favourite_remove_notification' => '":name" е премахнат от любими успешно', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'коментирано на', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'обновени права', ]; diff --git a/lang/bg/common.php b/lang/bg/common.php index e5b6ae087..49d9a4cd2 100644 --- a/lang/bg/common.php +++ b/lang/bg/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Премахване', 'add' => 'Добавяне', 'configure' => 'Конфигуриране', + 'manage' => 'Manage', 'fullscreen' => 'Цял екран', 'favourite' => 'Любимо', 'unfavourite' => 'Не е любимо', diff --git a/lang/bg/entities.php b/lang/bg/entities.php index c4107e111..6e5048e48 100644 --- a/lang/bg/entities.php +++ b/lang/bg/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Вмъкни рисунка', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Страницата не принадлежи в никоя глава', 'pages_move' => 'Премести страницата', 'pages_copy' => 'Копиране на страницата', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/bg/errors.php b/lang/bg/errors.php index cfeb7044d..d9efec43e 100644 --- a/lang/bg/errors.php +++ b/lang/bg/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Беше върната грешка, когато се изпрати тестовият емейл:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/bg/notifications.php b/lang/bg/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/bg/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/bg/preferences.php b/lang/bg/preferences.php index b38f35e48..26b421428 100644 --- a/lang/bg/preferences.php +++ b/lang/bg/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Преки пътища', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Запазване на преките пътища', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Обновени предпочитания за преки пътища!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/bg/settings.php b/lang/bg/settings.php index 1b97e68e2..12ddd678e 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Управление на настройките на приложението', 'role_export_content' => 'Експортирай съдържанието', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Настройки за достъп до активи', 'roles_system_warning' => 'Важно: Добавянето на потребител в някое от горните три роли може да му позволи да промени собствените си права или правата на другите в системата. Възлагайте тези роли само на доверени потребители.', 'role_asset_desc' => 'Тези настройки за достъп контролират достъпа по подразбиране до активите в системата. Настройките за достъп до книги, глави и страници ще отменят тези настройки.', diff --git a/lang/bs/activities.php b/lang/bs/activities.php index 54c09ec9f..bd1577436 100644 --- a/lang/bs/activities.php +++ b/lang/bs/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" je dodan u tvoje favorite', 'favourite_remove_notification' => '":name" je uklonjen iz tvojih favorita', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'je komentarisao/la na', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'je ažurirao/la dozvole', ]; diff --git a/lang/bs/common.php b/lang/bs/common.php index 144b94353..9dd94e214 100644 --- a/lang/bs/common.php +++ b/lang/bs/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Ukloni', 'add' => 'Dodaj', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Prikaz preko čitavog ekrana', 'favourite' => 'Favorit', 'unfavourite' => 'Ukloni favorit', diff --git a/lang/bs/entities.php b/lang/bs/entities.php index 7e34d22e7..a75c8aa49 100644 --- a/lang/bs/entities.php +++ b/lang/bs/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/bs/errors.php b/lang/bs/errors.php index f56708a64..4e013f10b 100644 --- a/lang/bs/errors.php +++ b/lang/bs/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Došlo je do greške prilikom slanja testnog e-maila:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/bs/notifications.php b/lang/bs/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/bs/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/bs/preferences.php b/lang/bs/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/bs/preferences.php +++ b/lang/bs/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/bs/settings.php b/lang/bs/settings.php index c110e8992..8821c77f0 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Manage app settings', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', diff --git a/lang/ca/activities.php b/lang/ca/activities.php index 677672dac..03174ff85 100644 --- a/lang/ca/activities.php +++ b/lang/ca/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'ha comentat a', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'ha actualitzat els permisos', ]; diff --git a/lang/ca/common.php b/lang/ca/common.php index e9393c940..010ec63ae 100644 --- a/lang/ca/common.php +++ b/lang/ca/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Elimina', 'add' => 'Afegeix', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Pantalla completa', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/ca/entities.php b/lang/ca/entities.php index 2a7f06a84..23a88b050 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insereix un diagrama', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'La pàgina no pertany a cap capítol', 'pages_move' => 'Mou la pàgina', 'pages_copy' => 'Copia la pàgina', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/ca/errors.php b/lang/ca/errors.php index 5bf33fb69..a716e240b 100644 --- a/lang/ca/errors.php +++ b/lang/ca/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'S\'ha produït un error en enviar un correu electrònic de prova:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ca/notifications.php b/lang/ca/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/ca/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/ca/preferences.php b/lang/ca/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/ca/preferences.php +++ b/lang/ca/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/ca/settings.php b/lang/ca/settings.php index fb67d3fdd..d93e52a73 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gestiona la configuració de l\'aplicació', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Permisos de recursos', 'roles_system_warning' => 'Tingueu en compte que l\'accés a qualsevol dels tres permisos de dalt pot permetre que un usuari alteri els seus propis permisos o els privilegis d\'altres usuaris del sistema. Assigneu rols amb aquests permisos només a usuaris de confiança.', 'role_asset_desc' => 'Aquests permisos controlen l\'accés per defecte als recursos del sistema. Els permisos de llibres, capítols i pàgines tindran més importància que aquests permisos.', diff --git a/lang/cs/activities.php b/lang/cs/activities.php index ca33971b0..18d537f64 100644 --- a/lang/cs/activities.php +++ b/lang/cs/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'obnovil/a stránku', 'page_restore_notification' => 'Stránka byla úspěšně obnovena', 'page_move' => 'přesunul/a stránku', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Strana byla úspěšně přesunuta', // Chapters 'chapter_create' => 'vytvořil/a kapitolu', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'odstranila/a kapitolu', 'chapter_delete_notification' => 'Kapitola byla úspěšně odstraněna', 'chapter_move' => 'přesunul/a kapitolu', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Kapitola byla úspěšně přesunuta', // Books 'book_create' => 'vytvořil/a knihu', @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Knihovna byla úspěšně smazána', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'obnovil revizi', + 'revision_delete' => 'odstranil revizi', + 'revision_delete_notification' => 'Revize byla úspěšně odstraněna', // Favourites 'favourite_add_notification' => '":name" byla přidána do Vašich oblíbených', 'favourite_remove_notification' => '":name" byla odstraněna z Vašich oblíbených', + // Watching + 'watch_update_level_notification' => 'Předvolby sledování úspěšně aktualizovány', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'se přihlásil', + 'auth_register' => 'se zaregistroval jako nový uživatel', + 'auth_password_reset_request' => 'zažádal o resetování hesla', + 'auth_password_reset_update' => 'zresetoval uživatelské heslo', + 'mfa_setup_method' => 'nastavil MFA metodu', 'mfa_setup_method_notification' => 'Vícefaktorová metoda byla úspěšně nakonfigurována', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'odstranil MFA metodu', 'mfa_remove_method_notification' => 'Vícefaktorová metoda byla úspěšně odstraněna', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'aktualizoval nastavení', + 'settings_update_notification' => 'Nastavení bylo úspěšně aktualizováno', + 'maintenance_action_run' => 'spustil údržbu', // Webhooks 'webhook_create' => 'vytvořil/a webhook', @@ -82,35 +85,40 @@ return [ 'webhook_delete_notification' => 'Webhook byl úspěšně odstraněn', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'vytvořil uživatele', + 'user_create_notification' => 'Uživatel byl úspěšně vytvořen', + 'user_update' => 'aktualizoval uživatele', 'user_update_notification' => 'Uživatel byl úspěšně aktualizován', - 'user_delete' => 'deleted user', + 'user_delete' => 'odstranil uživatele', 'user_delete_notification' => 'Uživatel byl úspěšně odstraněn', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'vytvořil api token', + 'api_token_create_notification' => 'API token úspěšně vytvořen', + 'api_token_update' => 'aktualizoval api token', + 'api_token_update_notification' => 'API token úspěšně aktualizován', + 'api_token_delete' => 'odstranil api token', + 'api_token_delete_notification' => 'API token úspěšně odstraněn', // Roles - 'role_create' => 'created role', + 'role_create' => 'vytvořil roli', 'role_create_notification' => 'Role byla úspěšně vytvořena', - 'role_update' => 'updated role', + 'role_update' => 'aktualizoval roli', 'role_update_notification' => 'Role byla úspěšně aktualizována', - 'role_delete' => 'deleted role', + 'role_delete' => 'odstranil roli', 'role_delete_notification' => 'Role byla odstraněna', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'vyprázdnil koš', + 'recycle_bin_restore' => 'obnovil z koše', + 'recycle_bin_destroy' => 'odstranil z koše', + + // Comments + 'commented_on' => 'okomentoval/a', + 'comment_create' => 'přidal komentář', + 'comment_update' => 'aktualizoval komentář', + 'comment_delete' => 'odstranil komentář', // Other - 'commented_on' => 'okomentoval/a', 'permissions_update' => 'oprávnění upravena', ]; diff --git a/lang/cs/common.php b/lang/cs/common.php index c033ca1e5..e7fc6ece8 100644 --- a/lang/cs/common.php +++ b/lang/cs/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Zrušit', - 'close' => 'Close', + 'close' => 'Zavřít‏', 'confirm' => 'Potvrdit', 'back' => 'Zpět', 'save' => 'Uložit', @@ -42,6 +42,7 @@ return [ 'remove' => 'Odebrat', 'add' => 'Přidat', 'configure' => 'Nastavit', + 'manage' => 'Spravovat', 'fullscreen' => 'Celá obrazovka', 'favourite' => 'Přidat do oblíbených', 'unfavourite' => 'Odebrat z oblíbených', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index cbf92c80f..1b526bd8c 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Vložit kresbu', 'pages_md_show_preview' => 'Zobrazit náhled', 'pages_md_sync_scroll' => 'Synchronizovat náhled', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Stránka není v kapitole', 'pages_move' => 'Přesunout stránku', 'pages_copy' => 'Kopírovat stránku', @@ -403,4 +405,28 @@ return [ 'references' => 'Odkazy', 'references_none' => 'Nebyly nalezeny žádné odkazy na tuto položku.', 'references_to_desc' => 'Níže jsou uvedeny všechny známé stránky systému, které odkazují na tuto položku.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/cs/errors.php b/lang/cs/errors.php index 670be2274..dfcddb9cb 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -49,16 +49,16 @@ return [ // Drawing & Images 'image_upload_error' => 'Nastala chyba během nahrávání souboru', 'image_upload_type_error' => 'Typ nahrávaného obrázku je neplatný.', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', + 'image_upload_replace_type' => 'Náhrady souboru obrázku musí být stejného typu', 'drawing_data_not_found' => 'Data výkresu nelze načíst. Výkresový soubor již nemusí existovat nebo nemusí mít oprávnění k němu přistupovat.', // Attachments 'attachment_not_found' => 'Příloha nenalezena', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_upload_error' => 'Nastala chyba během nahrávání přiloženého souboru', // Pages 'page_draft_autosave_fail' => 'Nepovedlo se uložit koncept. Než stránku uložíte, ujistěte se, že jste připojeni k internetu.', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'Nepodařilo se odstranit koncept stránky a načíst její aktuální obsah', 'page_custom_home_deletion' => 'Nelze odstranit tuto stránku, protože je nastavena jako uvítací stránka', // Entities @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Při posílání testovacího e-mailu nastala chyba:', + // HTTP errors + 'http_ssr_url_no_match' => 'URL adresa neodpovídá povoleným SSR poskytovatelům', ]; diff --git a/lang/cs/notifications.php b/lang/cs/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/cs/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/cs/preferences.php b/lang/cs/preferences.php index 6727470cf..ea529efce 100644 --- a/lang/cs/preferences.php +++ b/lang/cs/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Předvolby', + 'shortcuts' => 'Zkratky', 'shortcuts_interface' => 'Zobrazit klávesové zkratky', 'shortcuts_toggle_desc' => 'Zde můžete povolit nebo zakázat klávesové zkratky systémového rozhraní používané pro navigaci a akce.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Uložit zkratky', 'shortcuts_overlay_desc' => 'Poznámka: Když jsou povoleny zkratky, je k dispozici pomocný překryv stisknutím "? která zvýrazní dostupné zkratky pro akce viditelné na obrazovce.', 'shortcuts_update_success' => 'Nastavení pro zkratky bylo aktualizováno!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/cs/settings.php b/lang/cs/settings.php index 28766d7c7..2359af0b9 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Správa nastavení aplikace', 'role_export_content' => 'Exportovat obsah', 'role_editor_change' => 'Změnit editor stránek', + 'role_notifications' => 'Přijímat a spravovat oznámení', 'role_asset' => 'Obsahová oprávnění', 'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.', 'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.', diff --git a/lang/cy/activities.php b/lang/cy/activities.php index 9f7dcb9e5..2c474c673 100644 --- a/lang/cy/activities.php +++ b/lang/cy/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => 'Mae ":name" wedi\'i ychwanegu at eich ffefrynnau', 'favourite_remove_notification' => 'Mae ":name" wedi\'i tynnu o\'ch ffefrynnau', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'gwnaeth sylwadau ar', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'caniatadau wedi\'u diweddaru', ]; diff --git a/lang/cy/common.php b/lang/cy/common.php index de7937b2b..47b74d5b6 100644 --- a/lang/cy/common.php +++ b/lang/cy/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remove', 'add' => 'Add', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Fullscreen', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/cy/entities.php b/lang/cy/entities.php index 4fb043aa9..4468cd68f 100644 --- a/lang/cy/entities.php +++ b/lang/cy/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/cy/errors.php b/lang/cy/errors.php index ebf823dd9..6e687f4f2 100644 --- a/lang/cy/errors.php +++ b/lang/cy/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Gwall a daflwyd wrth anfon e-bost prawf:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/cy/notifications.php b/lang/cy/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/cy/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/cy/preferences.php b/lang/cy/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/cy/preferences.php +++ b/lang/cy/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/cy/settings.php b/lang/cy/settings.php index c110e8992..8821c77f0 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Manage app settings', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', diff --git a/lang/da/activities.php b/lang/da/activities.php index f27564132..48d159e8e 100644 --- a/lang/da/activities.php +++ b/lang/da/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" er blevet tilføjet til dine favoritter', 'favourite_remove_notification' => '":name" er blevet fjernet fra dine favoritter', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'kommenterede til', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'Tilladelser opdateret', ]; diff --git a/lang/da/common.php b/lang/da/common.php index 157558979..b9bb03e12 100644 --- a/lang/da/common.php +++ b/lang/da/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Fjern', 'add' => 'Tilføj', 'configure' => 'Konfigurer', + 'manage' => 'Manage', 'fullscreen' => 'Fuld skærm', 'favourite' => 'Foretrukken', 'unfavourite' => 'Fjern som foretrukken', diff --git a/lang/da/entities.php b/lang/da/entities.php index bc899d7fc..36791efbb 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Indsæt tegning', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Side er ikke i et kapitel', 'pages_move' => 'Flyt side', 'pages_copy' => 'Kopier side', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/da/errors.php b/lang/da/errors.php index 8b67f30e8..7c172c0aa 100644 --- a/lang/da/errors.php +++ b/lang/da/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Følgende fejl opstod under afsendelse af testemail:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/da/notifications.php b/lang/da/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/da/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/da/preferences.php b/lang/da/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/da/preferences.php +++ b/lang/da/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/da/settings.php b/lang/da/settings.php index ebc1f00a2..a121c1982 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Administrer app-indstillinger', 'role_export_content' => 'Eksporter indhold', 'role_editor_change' => 'Skift side editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Tilladelser for medier og "assets"', 'roles_system_warning' => 'Vær opmærksom på, at adgang til alle af de ovennævnte tre tilladelser, kan give en bruger mulighed for at ændre deres egne brugerrettigheder eller brugerrettigheder for andre i systemet. Tildel kun roller med disse tilladelser til betroede brugere.', 'role_asset_desc' => 'Disse tilladelser kontrollerer standardadgang til medier og "assets" i systemet. Tilladelser til bøger, kapitler og sider tilsidesætter disse tilladelser.', diff --git a/lang/de/activities.php b/lang/de/activities.php index 0582e1b26..b2fb996f1 100644 --- a/lang/de/activities.php +++ b/lang/de/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" wurde zu Ihren Favoriten hinzugefügt', 'favourite_remove_notification' => '":name" wurde aus Ihren Favoriten entfernt', + // Watching + 'watch_update_level_notification' => 'Beobachtungseinstellungen erfolgreich aktualisiert', + // Auth 'auth_login' => 'hat sich eingeloggt', 'auth_register' => 'hat sich als neuer Benutzer registriert', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'aus dem Papierkorb wiederhergestellt', 'recycle_bin_destroy' => 'aus dem Papierkorb gelöscht', - // Other + // Comments 'commented_on' => 'hat einen Kommentar hinzugefügt', + 'comment_create' => 'Kommentar hinzugefügt', + 'comment_update' => 'Kommentar aktualisiert', + 'comment_delete' => 'Kommentar gelöscht', + + // Other 'permissions_update' => 'hat die Berechtigungen aktualisiert', ]; diff --git a/lang/de/common.php b/lang/de/common.php index 6b159aa25..2946e4df6 100644 --- a/lang/de/common.php +++ b/lang/de/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Entfernen', 'add' => 'Hinzufügen', 'configure' => 'Konfigurieren', + 'manage' => 'Verwalten', 'fullscreen' => 'Vollbild', 'favourite' => 'Favoriten', 'unfavourite' => 'Kein Favorit', @@ -98,7 +99,7 @@ return [ 'tab_content_label' => 'Tab: Hauptinhalt anzeigen', // Email Content - 'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffnen Sie folgende URL in Ihrem Browser:', + 'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":actionText" Probleme geben, öffnen Sie folgende URL in Ihrem Browser:', 'email_rights' => 'Alle Rechte vorbehalten', // Footer Link Options diff --git a/lang/de/entities.php b/lang/de/entities.php index d2f790163..9e69196ac 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Regalberechtigungen aktualisiert', 'shelves_permissions_active' => 'Regalberechtigungen aktiv', 'shelves_permissions_cascade_warning' => 'Berechtigungen für Regale werden nicht automatisch auf die enthaltenen Bücher übertragen. Das liegt daran, dass ein Buch in mehreren Regalen vorhanden sein kann. Berechtigungen können jedoch auf untergeordnete Bücher kopiert werden, indem Sie die unten stehende Option verwenden.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Regalerstellungsberechtigungen werden nur zum Kopieren von Berechtigungen für untergeordnete Bücher mit der folgenden Aktion verwendet. Sie kontrollieren nicht die Fähigkeit, Bücher zu erstellen.', 'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch', 'shelves_copy_permissions' => 'Berechtigungen kopieren', 'shelves_copy_permissions_explain' => 'Dadurch werden die aktuellen Berechtigungseinstellungen dieses Regals auf alle darin enthaltenen Bücher angewendet. Vergewissern Sie sich vor der Aktivierung, dass alle Änderungen an den Berechtigungen für dieses Regal gespeichert wurden.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Zeichnung einfügen', 'pages_md_show_preview' => 'Vorschau anzeigen', 'pages_md_sync_scroll' => 'Vorschau synchronisieren', + 'pages_drawing_unsaved' => 'Ungespeicherte Zeichnung gefunden', + 'pages_drawing_unsaved_confirm' => 'Es wurden ungespeicherte Zeichnungsdaten von einem früheren, fehlgeschlagenen Versuch, die Zeichnung zu speichern, gefunden. Möchten Sie diese ungespeicherte Zeichnung wiederherstellen und weiter bearbeiten?', 'pages_not_in_chapter' => 'Seite ist in keinem Kapitel', 'pages_move' => 'Seite verschieben', 'pages_copy' => 'Seite kopieren', @@ -403,4 +405,28 @@ return [ 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', 'references_to_desc' => 'Nachfolgend sind alle bekannten Seiten im System aufgeführt, die auf diesen Artikel verweisen.', + + // Watch Options + 'watch' => 'Beobachten', + 'watch_title_default' => 'Standardeinstellungen', + 'watch_desc_default' => 'Rückgängig machen auf Standard-Benachrichtigungseinstellungen.', + 'watch_title_ignore' => 'Ignorieren', + 'watch_desc_ignore' => 'Ignorieren aller Benachrichtigungen, auch die von den Einstellungen auf Benutzerebene.', + 'watch_title_new' => 'Neue Seiten', + 'watch_desc_new' => 'Benachrichtigung, wenn eine neue Seite in diesem Element erstellt wird.', + 'watch_title_updates' => 'Alle Seitenupdates', + 'watch_desc_updates' => 'Bei allen neuen Seiten und Seitenänderungen benachrichtigen.', + 'watch_desc_updates_page' => 'Bei allen Seitenänderungen benachrichtigen.', + 'watch_title_comments' => 'Alle Seitenupdates & Kommentare', + 'watch_desc_comments' => 'Benachrichtigung bei allen neuen Seiten, Seitenänderungen und neuen Kommentaren.', + 'watch_desc_comments_page' => 'Benachrichtigung bei Seitenänderungen und neuen Kommentaren.', + 'watch_change_default' => 'Standard-Benachrichtigungseinstellungen ändern', + 'watch_detail_ignore' => 'Benachrichtigungen ignorieren', + 'watch_detail_new' => 'Beobachten von neuen Seiten', + 'watch_detail_updates' => 'Beobachtung neuer Seiten und Aktualisierungen', + 'watch_detail_comments' => 'Beobachtung neuer Seiten, Aktualisierungen und Kommentare', + 'watch_detail_parent_book' => 'Beobachten über übergeordnetes Buch', + 'watch_detail_parent_book_ignore' => 'Ignorieren über übergeordnetes Buch', + 'watch_detail_parent_chapter' => 'Beobachten über übergeordnetes Kapitel', + 'watch_detail_parent_chapter_ignore' => 'Ignorieren über übergeordnetes Kapitel', ]; diff --git a/lang/de/errors.php b/lang/de/errors.php index 1d6c7f422..04e97c047 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Versenden einer Test E-Mail:', + // HTTP errors + 'http_ssr_url_no_match' => 'Die URL stimmt nicht mit den konfigurierten erlaubten SSR-Hosts überein', ]; diff --git a/lang/de/notifications.php b/lang/de/notifications.php new file mode 100644 index 000000000..314f0bfe3 --- /dev/null +++ b/lang/de/notifications.php @@ -0,0 +1,26 @@ + 'Neuer Kommentar auf Seite: :pageName', + 'new_comment_intro' => 'Ein Benutzer hat eine Seite in :appName kommentiert:', + 'new_page_subject' => 'Neue Seite: :pageName', + 'new_page_intro' => 'Es wurde eine neue Seite in :appName erstellt:', + 'updated_page_subject' => 'Aktualisierte Seite: :pageName', + 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', + 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, werden Sie für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', + + 'detail_page_name' => 'Name der Seite:', + 'detail_commenter' => 'Kommentator:', + 'detail_comment' => 'Kommentar:', + 'detail_created_by' => 'Erstellt von:', + 'detail_updated_by' => 'Aktualisiert von:', + + 'action_view_comment' => 'Kommentar anzeigen', + 'action_view_page' => 'Seite anzeigen', + + 'footer_reason' => 'Diese Benachrichtigung wurde an Sie gesendet, weil :link diese Art von Aktivität für dieses Element abdeckt.', + 'footer_reason_link' => 'ihre Benachrichtigungseinstellungen', +]; diff --git a/lang/de/preferences.php b/lang/de/preferences.php index 31ae2272d..8eac9725d 100644 --- a/lang/de/preferences.php +++ b/lang/de/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Profil-Einstellungen', + 'shortcuts' => 'Tastenkürzel', 'shortcuts_interface' => 'Oberflächen-Tastaturkürzel', 'shortcuts_toggle_desc' => 'Hier können Sie Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Tastenkürzel speichern', 'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfsoverlay durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.', 'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Verwalten von Tastenkombinationen, die zur Navigation der Benutzeroberfläche verwendet werden können.', + + 'notifications' => 'Benachrichtigungseinstellungen', + 'notifications_desc' => 'Legen Sie fest, welche E-Mail-Benachrichtigungen Sie erhalten, wenn bestimmte Aktivitäten im System durchgeführt werden.', + 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', + 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', + 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', + 'notifications_save' => 'Einstellungen speichern', + 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', + 'notifications_watched' => 'Beobachtete und ignorierte Elemente', + 'notifications_watched_desc' => ' Nachfolgend finden Sie die Elemente, für die benutzerdefinierten Überwachungspräferenzen gelten. Um Ihre Einstellungen für diese Elemente zu aktualisieren, sehen Sie sich das Element an und suchen dann die Überwachungsoptionen in der Seitenleiste.', + + 'profile_overview_desc' => ' Verwalten Sie die Details Ihres Benutzerprofils einschließlich bevorzugter Sprache und Authentifizierungsoptionen.', +]; diff --git a/lang/de/settings.php b/lang/de/settings.php index 00c5cde2d..7789c7b65 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -164,6 +164,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung 'role_manage_settings' => 'Globaleinstellungen verwalten', 'role_export_content' => 'Inhalt exportieren', 'role_editor_change' => 'Seiten-Editor ändern', + 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', 'role_asset' => 'Berechtigungen', 'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.', 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.', diff --git a/lang/de_informal/activities.php b/lang/de_informal/activities.php index 48fd24315..a176a75f7 100644 --- a/lang/de_informal/activities.php +++ b/lang/de_informal/activities.php @@ -8,35 +8,35 @@ return [ // Pages 'page_create' => 'erstellt Seite', 'page_create_notification' => 'Seite erfolgreich erstellt', - 'page_update' => 'aktualisiert Seite', + 'page_update' => 'Seite aktualisiert', 'page_update_notification' => 'Seite erfolgreich aktualisiert', - 'page_delete' => 'löscht Seite', + 'page_delete' => 'Seite gelöscht', 'page_delete_notification' => 'Seite erfolgreich gelöscht', - 'page_restore' => 'stellt Seite wieder her', + 'page_restore' => 'Seite wiederhergestellt', 'page_restore_notification' => 'Seite erfolgreich wiederhergestellt', - 'page_move' => 'verschiebt Seite', + 'page_move' => 'Seite verschoben', 'page_move_notification' => 'Seite erfolgreich verschoben', // Chapters - 'chapter_create' => 'erstellt Kapitel', + 'chapter_create' => 'Kapitel erstellt', 'chapter_create_notification' => 'Kapitel erfolgreich erstellt', - 'chapter_update' => 'aktualisiert Kapitel', + 'chapter_update' => 'Kapitel aktualisiert', 'chapter_update_notification' => 'Kapitel erfolgreich aktualisiert', - 'chapter_delete' => 'löscht Kapitel', + 'chapter_delete' => 'Kapitel gelöscht', 'chapter_delete_notification' => 'Kapitel erfolgreich gelöscht', - 'chapter_move' => 'verschiebt Kapitel', + 'chapter_move' => 'Kapitel verschoben', 'chapter_move_notification' => 'Kapitel erfolgreich verschoben', // Books - 'book_create' => 'erstellt Buch', + 'book_create' => 'Buch erstellt', 'book_create_notification' => 'Buch erfolgreich erstellt', 'book_create_from_chapter' => 'Kapitel zu Buch umgewandelt', 'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch umgewandelt', - 'book_update' => 'aktualisiert Buch', + 'book_update' => 'Buch aktualisiert', 'book_update_notification' => 'Buch erfolgreich aktualisiert', - 'book_delete' => 'löscht Buch', + 'book_delete' => 'Buch gelöscht', 'book_delete_notification' => 'Buch erfolgreich gelöscht', - 'book_sort' => 'sortiert Buch', + 'book_sort' => 'Buch sortiert', 'book_sort_notification' => 'Buch erfolgreich umsortiert', // Bookshelves @@ -50,19 +50,22 @@ return [ 'bookshelf_delete_notification' => 'Regal erfolgreich gelöscht', // Revisions - 'revision_restore' => 'stellte Revision wieder her:', - 'revision_delete' => 'löschte Revision', + 'revision_restore' => 'Revision wiederhergestellt', + 'revision_delete' => 'Revision gelöscht', 'revision_delete_notification' => 'Revision erfolgreich gelöscht', // Favourites 'favourite_add_notification' => '":name" wurde zu deinen Favoriten hinzugefügt', 'favourite_remove_notification' => '":name" wurde aus deinen Favoriten entfernt', + // Watching + 'watch_update_level_notification' => 'Beobachtungseinstellungen erfolgreich aktualisiert', + // Auth 'auth_login' => 'hat sich eingeloggt', 'auth_register' => 'hat sich als neuer Benutzer registriert', 'auth_password_reset_request' => 'hat eine Rücksetzung des Benutzerpassworts beantragt', - 'auth_password_reset_update' => 'hat Benutzerpasswort zurückgesetzt', + 'auth_password_reset_update' => 'Benutzerpasswort zurückgesetzt', 'mfa_setup_method' => 'hat MFA-Methode konfiguriert', 'mfa_setup_method_notification' => 'Multi-Faktor-Methode erfolgreich konfiguriert', 'mfa_remove_method' => 'hat MFA-Methode entfernt', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'aus dem Papierkorb wiederhergestellt', 'recycle_bin_destroy' => 'aus dem Papierkorb gelöscht', - // Other + // Comments 'commented_on' => 'kommentiert', + 'comment_create' => 'Kommentar hinzugefügt', + 'comment_update' => 'Kommentar aktualisiert', + 'comment_delete' => 'Kommentar gelöscht', + + // Other 'permissions_update' => 'aktualisierte Berechtigungen', ]; diff --git a/lang/de_informal/common.php b/lang/de_informal/common.php index fb77aa8c6..cfa28453f 100644 --- a/lang/de_informal/common.php +++ b/lang/de_informal/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Entfernen', 'add' => 'Hinzufügen', 'configure' => 'Konfigurieren', + 'manage' => 'Verwalten', 'fullscreen' => 'Vollbild', 'favourite' => 'Favoriten', 'unfavourite' => 'Kein Favorit', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 949bc1473..5b68a4fbf 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Regalberechtigungen aktualisiert', 'shelves_permissions_active' => 'Regalberechtigungen aktiv', 'shelves_permissions_cascade_warning' => 'Berechtigungen für Regale werden nicht automatisch auf die enthaltenen Bücher übertragen. Das liegt daran, dass ein Buch in mehreren Regalen vorhanden sein kann. Berechtigungen können jedoch auf untergeordnete Bücher kopiert werden, indem du die unten stehende Option verwendest.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => '"Regal erstellen"-Berechtigungen werden nur zum Kopieren von Berechtigungen für untergeordnete Bücher mit der folgenden Aktion verwendet. Sie kontrollieren nicht die Fähigkeit, Bücher zu erstellen.', 'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch', 'shelves_copy_permissions' => 'Berechtigungen kopieren', 'shelves_copy_permissions_explain' => 'Dadurch werden die aktuellen Berechtigungseinstellungen dieses Regals auf alle darin enthaltenen Bücher angewendet. Vergewissere dich vor der Aktivierung, dass alle Änderungen an den Berechtigungen für dieses Regal gespeichert wurden.', @@ -116,7 +116,7 @@ return [ 'book' => 'Buch', 'books' => 'Bücher', 'x_books' => ':count Buch|:count Bücher', - 'books_empty' => 'Keine Bücher vorhanden', + 'books_empty' => 'Es wurden noch keine Bücher angelegt', 'books_popular' => 'Beliebte Bücher', 'books_recent' => 'Kürzlich angesehene Bücher', 'books_new' => 'Neue Bücher', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Zeichnung einfügen', 'pages_md_show_preview' => 'Vorschau anzeigen', 'pages_md_sync_scroll' => 'Vorschau synchronisieren', + 'pages_drawing_unsaved' => 'Ungespeicherte Zeichnung gefunden', + 'pages_drawing_unsaved_confirm' => 'Es wurden ungespeicherte Zeichnungsdaten von einem früheren, fehlgeschlagenen Versuch, die Zeichnung zu speichern, gefunden. Möchtest du diese ungespeicherte Zeichnung wiederherstellen und weiter bearbeiten?', 'pages_not_in_chapter' => 'Seite ist in keinem Kapitel', 'pages_move' => 'Seite verschieben', 'pages_copy' => 'Seite kopieren', @@ -403,4 +405,28 @@ return [ 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', 'references_to_desc' => 'Nachfolgend sind alle bekannten Seiten im System aufgeführt, die auf diesen Artikel verweisen.', + + // Watch Options + 'watch' => 'Beobachten', + 'watch_title_default' => 'Standardeinstellungen', + 'watch_desc_default' => 'Rückgängig machen auf Standard-Benachrichtigungseinstellungen.', + 'watch_title_ignore' => 'Ignorieren', + 'watch_desc_ignore' => 'Ignorieren aller Benachrichtigungen, auch die von den Einstellungen auf Benutzerebene.', + 'watch_title_new' => 'Neue Seiten', + 'watch_desc_new' => 'Benachrichtigen, wenn eine neue Seite in diesem Element erstellt wird.', + 'watch_title_updates' => 'Alle Seitenupdates', + 'watch_desc_updates' => 'Bei allen neuen Seiten und Seitenänderungen benachrichtigen.', + 'watch_desc_updates_page' => 'Bei allen Seitenänderungen benachrichtigen.', + 'watch_title_comments' => 'Alle Seitenupdates & Kommentare', + 'watch_desc_comments' => 'Benachrichtigung bei allen neuen Seiten, Seitenänderungen und neuen Kommentaren.', + 'watch_desc_comments_page' => 'Benachrichtigung bei Seitenänderungen und neuen Kommentaren.', + 'watch_change_default' => 'Standard-Benachrichtigungseinstellungen ändern', + 'watch_detail_ignore' => 'Benachrichtigungen ignorieren', + 'watch_detail_new' => 'Beobachten von neuen Seiten', + 'watch_detail_updates' => 'Beobachtung neuer Seiten und Aktualisierungen', + 'watch_detail_comments' => 'Beobachtung neuer Seiten, Aktualisierungen und Kommentare', + 'watch_detail_parent_book' => 'Beobachten über übergeordnetes Buch', + 'watch_detail_parent_book_ignore' => 'Ignorieren über übergeordnetes Buch', + 'watch_detail_parent_chapter' => 'Beobachten über übergeordnetes Kapitel', + 'watch_detail_parent_chapter_ignore' => 'Ignorieren über übergeordnetes Kapitel', ]; diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index 3d4d6dee5..c19c0eda8 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Senden einer Test E-Mail:', + // HTTP errors + 'http_ssr_url_no_match' => 'Die URL stimmt nicht mit den konfigurierten erlaubten SSR-Hosts überein', ]; diff --git a/lang/de_informal/notifications.php b/lang/de_informal/notifications.php new file mode 100644 index 000000000..fc6204d50 --- /dev/null +++ b/lang/de_informal/notifications.php @@ -0,0 +1,26 @@ + 'Neuer Kommentar auf Seite: :pageName', + 'new_comment_intro' => 'Ein Benutzer hat eine Seite in :appName kommentiert:', + 'new_page_subject' => 'Neue Seite: :pageName', + 'new_page_intro' => 'Es wurde eine neue Seite in :appName erstellt:', + 'updated_page_subject' => 'Aktualisierte Seite: :pageName', + 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', + 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', + + 'detail_page_name' => 'Seitenname:', + 'detail_commenter' => 'Kommentator:', + 'detail_comment' => 'Kommentar:', + 'detail_created_by' => 'Erstellt von:', + 'detail_updated_by' => 'Aktualisiert von:', + + 'action_view_comment' => 'Kommentar anzeigen', + 'action_view_page' => 'Seite anzeigen', + + 'footer_reason' => 'Diese Benachrichtigung wurde an dich gesendet, weil :link diese Art von Aktivität für dieses Element abdeckt.', + 'footer_reason_link' => 'deine Benachrichtigungseinstellungen', +]; diff --git a/lang/de_informal/passwords.php b/lang/de_informal/passwords.php index 0dd28c019..e3606760b 100644 --- a/lang/de_informal/passwords.php +++ b/lang/de_informal/passwords.php @@ -6,7 +6,7 @@ */ return [ - 'password' => 'Passwörter müssen aus mindestens sechs Zeichen bestehen und mit der eingegebenen Wiederholung übereinstimmen.', + 'password' => 'Passwörter müssen aus mindestens acht Zeichen bestehen und mit der eingegebenen Wiederholung übereinstimmen.', 'user' => "Es wurde kein Benutzer mit dieser E-Mail-Adresse gefunden.", 'token' => 'Der Token zum Zurücksetzen des Passworts für diese E-Mail-Adresse ist ungültig.', 'sent' => 'Wir haben dir einen Link zum Zurücksetzen des Passwortes per E-Mail geschickt!', diff --git a/lang/de_informal/preferences.php b/lang/de_informal/preferences.php index 5670fcee5..cf2962482 100644 --- a/lang/de_informal/preferences.php +++ b/lang/de_informal/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Profil-Einstellungen', + 'shortcuts' => 'Kürzel', 'shortcuts_interface' => 'Oberflächen-Tastaturkürzel', 'shortcuts_toggle_desc' => 'Hier kannst du Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Tastenkürzel speichern', 'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfefähnchen durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.', 'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Verwalten von Tastenkombinationen, die zur Navigation der Benutzeroberfläche verwendet werden können.', + + 'notifications' => 'Benachrichtigungseinstellungen', + 'notifications_desc' => 'Lege fest, welche E-Mail-Benachrichtigungen du erhältst, wenn bestimmte Aktivitäten im System durchgeführt werden.', + 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', + 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', + 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', + 'notifications_save' => 'Einstellungen speichern', + 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', + 'notifications_watched' => 'Beobachtete und ignorierte Elemente', + 'notifications_watched_desc' => ' Nachfolgend finden Sie die Elemente, für die benutzerdefinierten Überwachungspräferenzen gelten. Um deine Einstellungen für diese Elemente zu aktualisieren, sieh dir das Element an und suche dann die Überwachungsoptionen in der Seitenleiste.', + + 'profile_overview_desc' => ' Verwalte die Details deines Benutzerprofils, einschließlich bevorzugter Sprache und Authentifizierungsoptionen.', +]; diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index f9e3f438e..54e90fab0 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -164,6 +164,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 'role_manage_settings' => 'Globaleinstellungen verwalten', 'role_export_content' => 'Inhalt exportieren', 'role_editor_change' => 'Seiteneditor ändern', + 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', 'role_asset' => 'Berechtigungen', 'roles_system_warning' => 'Beachte, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weise nur Rollen mit diesen Berechtigungen vertrauenswürdigen Benutzern zu.', 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungen.', diff --git a/lang/el/activities.php b/lang/el/activities.php index b90448f01..3bb66da8b 100644 --- a/lang/el/activities.php +++ b/lang/el/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'αποκατεστημένη σελίδα', 'page_restore_notification' => 'Η σελίδα αποκαταστάθηκε με επιτυχία', 'page_move' => 'Η σελίδα μετακινήθηκε', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Η σελίδα μετακινήθηκε με επιτυχία', // Chapters 'chapter_create' => 'δημιουργήθηκε κεφάλαιο', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'διαγραμμένο κεφάλαιο', 'chapter_delete_notification' => 'Το κεφάλαιο διαγράφηκε επιτυχώς', 'chapter_move' => 'το κεφάλαιο μετακινήθηκε', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Το κεφάλαιο μετακινήθηκε με επιτυχία', // Books 'book_create' => 'το βιβλίο δημιουργήθηκε', @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Το ράφι ενημερώθηκε επιτυχώς', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'αποκατεστημένη αναθεώρηση', + 'revision_delete' => 'διαγραμμένη αναθεώρηση', + 'revision_delete_notification' => 'Η αναθεώρηση διαγράφηκε με επιτυχία', // Favourites 'favourite_add_notification' => '":name" προστέθηκε στα αγαπημένα σας', 'favourite_remove_notification' => '":name" προστέθηκε στα αγαπημένα σας', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'συνδεδεμένος', + 'auth_register' => 'εγγεγραμμένος ως νέος χρήστης', + 'auth_password_reset_request' => 'ζητήθηκε επαναφορά κωδικού πρόσβασης χρήστη', + 'auth_password_reset_update' => 'επαναφορά κωδικού πρόσβασης χρήστη', + 'mfa_setup_method' => 'διαμορφωμένη μέθοδος MFA', 'mfa_setup_method_notification' => 'Η μέθοδος πολλαπλών παραγόντων διαμορφώθηκε επιτυχώς', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'καταργήθηκε η μέθοδος MFA', 'mfa_remove_method_notification' => 'Η μέθοδος πολλαπλών παραγόντων καταργήθηκε με επιτυχία', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'ενημερωμένες ρυθμίσεις', + 'settings_update_notification' => 'Οι ρυθμίσεις ενημερώθηκαν με επιτυχία', + 'maintenance_action_run' => 'έτρεξε δράση συντήρησης', // Webhooks 'webhook_create' => 'Το webhook δημιουργήθηκε', @@ -82,25 +85,25 @@ return [ 'webhook_delete_notification' => 'Το Webhook διαγράφηκε επιτυχώς', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'δημιουργημένος χρήστης', + 'user_create_notification' => 'Ο χρήστης δημιουργήθηκε με επιτυχία', + 'user_update' => 'ενημερωμένος χρήστης', 'user_update_notification' => 'Ο Χρήστης ενημερώθηκε με επιτυχία', - 'user_delete' => 'deleted user', + 'user_delete' => 'διαγραμμένος χρήστης', 'user_delete_notification' => 'Ο Χρήστης αφαιρέθηκε επιτυχώς', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'δημιουργήθηκε κωδικός API', + 'api_token_create_notification' => 'O κωδικός API δημιουργήθηκε με επιτυχία', + 'api_token_update' => 'κωδικός API ενημερώθηκε', + 'api_token_update_notification' => 'κωδικός API ενημερώθηκε με επιτυχία', + 'api_token_delete' => 'διαγραμμένο api token', + 'api_token_delete_notification' => 'Το διακριτικό API διαγράφηκε με επιτυχία', // Roles - 'role_create' => 'created role', + 'role_create' => 'δημιουργημένος ρόλος', 'role_create_notification' => 'Ο Ρόλος δημιουργήθηκε με επιτυχία', - 'role_update' => 'updated role', + 'role_update' => 'Ενημέρωση ρόλου', 'role_update_notification' => 'Ο Ρόλος ενημερώθηκε με επιτυχία', 'role_delete' => 'deleted role', 'role_delete_notification' => 'Ο Ρόλος διαγράφηκε επιτυχώς', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'σχολίασε', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'ενημερωμένα δικαιώματα', ]; diff --git a/lang/el/common.php b/lang/el/common.php index 45bcb85f1..523e83add 100644 --- a/lang/el/common.php +++ b/lang/el/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Αφαίρεση', 'add' => 'Προσθήκη', 'configure' => 'Διαμόρφωση', + 'manage' => 'Manage', 'fullscreen' => 'Πλήρης οθόνη', 'favourite' => 'Αγαπημένα', 'unfavourite' => 'Αφαίρεση από Αγαπημένα', diff --git a/lang/el/entities.php b/lang/el/entities.php index 07c18c048..42163eaf0 100644 --- a/lang/el/entities.php +++ b/lang/el/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Εισαγωγή Σχεδίου', 'pages_md_show_preview' => 'Εμφάνιση προεπισκόπησης', 'pages_md_sync_scroll' => 'Συγχρονισμός προεπισκόπησης', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Η σελίδα δεν είναι σε κεφάλαιο', 'pages_move' => 'Μετακίνηση Σελίδας', 'pages_copy' => 'Αντιγραφή Σελίδας', @@ -403,4 +405,28 @@ return [ 'references' => 'Αναφορές', 'references_none' => 'Δεν υπάρχουν αναφορές παρακολούθησης σε αυτό το στοιχείο.', 'references_to_desc' => 'Παρακάτω εμφανίζονται όλες οι γνωστές σελίδες του συστήματος που συνδέονται με αυτό το στοιχείο.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/el/errors.php b/lang/el/errors.php index a30a44ebe..9d98015a0 100644 --- a/lang/el/errors.php +++ b/lang/el/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Σφάλμα κατά την αποστολή δοκιμαστικού email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/el/notifications.php b/lang/el/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/el/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/el/preferences.php b/lang/el/preferences.php index f9db475aa..2ead9f17b 100644 --- a/lang/el/preferences.php +++ b/lang/el/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Συντομεύσεις', 'shortcuts_interface' => 'Συντομεύσεις Πληκτρολογίου Διεπαφής', 'shortcuts_toggle_desc' => 'Εδώ μπορείτε να ενεργοποιήσετε ή να απενεργοποιήσετε τις συντομεύσεις του συστήματος πληκτρολογίου, που χρησιμοποιούνται για την πλοήγηση και τις ενέργειες.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Αποθήκευση Συντομεύσεων', 'shortcuts_overlay_desc' => 'Σημείωση: Όταν οι συντομεύσεις είναι ενεργοποιημένες μια βοηθητική επικάλυψη είναι διαθέσιμη πατώντας "?" που θα τονίσει τις διαθέσιμες συντομεύσεις για ενέργειες που είναι ορατές στην οθόνη.', 'shortcuts_update_success' => 'Οι προτιμήσεις σας αποθηκεύτηκαν!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/el/settings.php b/lang/el/settings.php index 080d2cddb..24913e222 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Διαχειριστείτε τις ρυθμίσεις του ΑΡΙ', 'role_export_content' => 'Εξαγωγή περιεχομένου', 'role_editor_change' => 'Αλλαγή προγράμματος επεξεργασίας σελίδας', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Δικαιώματα Συστήματος', 'roles_system_warning' => 'Λάβετε υπόψη ότι η πρόσβαση σε οποιοδήποτε από τις τρεις παραπάνω άδειες (δικαιώματα) μπορεί να επιτρέψει σε έναν χρήστη να αλλάξει τα δικά του προνόμια ή τα προνόμια άλλων στο σύστημα. Εκχωρήστε ρόλους με αυτά τα δικαιώματα μόνο σε αξιόπιστους χρήστες.', 'role_asset_desc' => 'Αυτά τα δικαιώματα ελέγχουν την προεπιλεγμένη πρόσβαση στα στοιχεία (άδειες) εντός του συστήματος. Τα δικαιώματα σε Βιβλία, Κεφάλαια και Σελίδες θα παρακάμψουν αυτές τις άδειες.', diff --git a/lang/en/activities.php b/lang/en/activities.php index a96299ea7..d5b55c03d 100644 --- a/lang/en/activities.php +++ b/lang/en/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', diff --git a/lang/en/common.php b/lang/en/common.php index de7937b2b..47b74d5b6 100644 --- a/lang/en/common.php +++ b/lang/en/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remove', 'add' => 'Add', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Fullscreen', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/en/entities.php b/lang/en/entities.php index 4fb043aa9..4468cd68f 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/en/errors.php b/lang/en/errors.php index 23c326f9e..4cde4cea3 100644 --- a/lang/en/errors.php +++ b/lang/en/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/en/notifications.php b/lang/en/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/en/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/en/preferences.php b/lang/en/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/en/preferences.php +++ b/lang/en/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/en/settings.php b/lang/en/settings.php index c110e8992..9f60606ac 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Manage app settings', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', @@ -313,6 +314,7 @@ return [ 'sv' => 'Svenska', 'tr' => 'Türkçe', 'uk' => 'Українська', + 'uz' => 'O‘zbekcha', 'vi' => 'Tiếng Việt', 'zh_CN' => '简体中文', 'zh_TW' => '繁體中文', diff --git a/lang/es/activities.php b/lang/es/activities.php index 420e30791..a83258212 100644 --- a/lang/es/activities.php +++ b/lang/es/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '".name" ha sido añadido a sus favoritos', 'favourite_remove_notification' => '".name" ha sido eliminado de sus favoritos', + // Watching + 'watch_update_level_notification' => 'Preferencias de suscripciones actualizadas correctamente', + // Auth 'auth_login' => 'conectado', 'auth_register' => 'registrado como nuevo usuario', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restaurado de la papelera de reciclaje', 'recycle_bin_destroy' => 'eliminado de la papelera de reciclaje', - // Other + // Comments 'commented_on' => 'comentada el', + 'comment_create' => 'comentario añadido', + 'comment_update' => 'comentario actualizado', + 'comment_delete' => 'comentario borrado', + + // Other 'permissions_update' => 'permisos actualizados', ]; diff --git a/lang/es/common.php b/lang/es/common.php index a0cb5ae4c..fac8bf259 100644 --- a/lang/es/common.php +++ b/lang/es/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remover', 'add' => 'Añadir', 'configure' => 'Configurar', + 'manage' => 'Gestionar', 'fullscreen' => 'Pantalla completa', 'favourite' => 'Añadir a favoritos', 'unfavourite' => 'Eliminar de favoritos', diff --git a/lang/es/entities.php b/lang/es/entities.php index f37a3539d..c02ce064d 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permisos del estante actualizados', 'shelves_permissions_active' => 'Permisos del estante activos', 'shelves_permissions_cascade_warning' => 'Los permisos en los estantes no se aplican automáticamente a los libros que contengan. Esto se debe a que un libro puede existir en múltiples estantes. Sin embargo, los permisos pueden ser aplicados a los libros del estante utilizando la opción a continuación.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Los permisos de creación de estantes sólo se utilizan para copiar los permisos a los libros contenidos utilizando la acción a continuación. No controlan la capacidad de crear libros.', 'shelves_copy_permissions_to_books' => 'Copiar permisos a los libros', 'shelves_copy_permissions' => 'Copiar permisos', 'shelves_copy_permissions_explain' => 'Esto aplicará los permisos de este estante para todos sus libros. Antes de activarlo, asegúrese de que todos los cambios de permisos para este estante han sido guardados.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insertar Dibujo', 'pages_md_show_preview' => 'Mostrar vista previa', 'pages_md_sync_scroll' => 'Sincronizar desplazamiento de vista previa', + 'pages_drawing_unsaved' => 'Encontrado dibujo sin guardar', + 'pages_drawing_unsaved_confirm' => 'Se encontraron datos no guardados del dibujo de un intento de guardado fallido. ¿Desea restaurar y continuar editando el dibujo no guardado?', 'pages_not_in_chapter' => 'La página no está en un capítulo', 'pages_move' => 'Mover página', 'pages_copy' => 'Copiar página', @@ -403,4 +405,28 @@ return [ 'references' => 'Referencias', 'references_none' => 'No hay referencias a este elemento.', 'references_to_desc' => 'A continuación se muestran todas las páginas en el sistema que enlazan a este elemento.', + + // Watch Options + 'watch' => 'Suscribirme', + 'watch_title_default' => 'Preferencias por defecto', + 'watch_desc_default' => 'Revertir suscripciones a tus preferencias de notificación por defecto.', + 'watch_title_ignore' => 'Ignorar', + 'watch_desc_ignore' => 'Ignorar todas las notificaciones, incluyendo las de las preferencias a nivel de usuario.', + 'watch_title_new' => 'Nuevas páginas', + 'watch_desc_new' => 'Notificar cuando se crea una nueva página dentro de este elemento.', + 'watch_title_updates' => 'Todas las actualizaciones de páginas', + 'watch_desc_updates' => 'Notificar todos los cambios de páginas y páginas nuevas.', + 'watch_desc_updates_page' => 'Notificar todos los cambios en la página.', + 'watch_title_comments' => 'Todas las actualizaciones de páginas y comentarios', + 'watch_desc_comments' => 'Notificar sobre todas las páginas nuevas, cambios de página y nuevos comentarios.', + 'watch_desc_comments_page' => 'Notificar los cambios en las páginas y los nuevos comentarios.', + 'watch_change_default' => 'Cambiar preferencias de notificación por defecto', + 'watch_detail_ignore' => 'Ignorar notificaciones', + 'watch_detail_new' => 'Suscripciones de nuevas páginas', + 'watch_detail_updates' => 'Suscripciones de nuevas páginas y actualizaciones de páginas', + 'watch_detail_comments' => 'Suscripciones de nuevas páginas, actualizaciones de páginas y comentarios', + 'watch_detail_parent_book' => 'Subscripciones por libro contenedor', + 'watch_detail_parent_book_ignore' => 'Ignorando a través del libro contenedor', + 'watch_detail_parent_chapter' => 'Subscripciones por capítulo superior', + 'watch_detail_parent_chapter_ignore' => 'Ignorar por capítulo superior', ]; diff --git a/lang/es/errors.php b/lang/es/errors.php index e0006937b..74fdd0e9d 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', + // HTTP errors + 'http_ssr_url_no_match' => 'La URL no coincide con los hosts SSR permitidos', ]; diff --git a/lang/es/notifications.php b/lang/es/notifications.php new file mode 100644 index 000000000..4e76a5924 --- /dev/null +++ b/lang/es/notifications.php @@ -0,0 +1,26 @@ + 'Nuevo comentario en la página: :pageName', + 'new_comment_intro' => 'Un usuario ha comentado en una página de :appName:', + 'new_page_subject' => 'Nueva página: :pageName', + 'new_page_intro' => 'Una nueva página ha sido creada en :appName:', + 'updated_page_subject' => 'Página actualizada: :pageName', + 'updated_page_intro' => 'Una página ha sido actualizada en :appName:', + 'updated_page_debounce' => 'Para prevenir notificaciones en masa, durante un tiempo no se enviarán notificaciones para futuras ediciones de esta página por el mismo editor.', + + 'detail_page_name' => 'Nombre de página:', + 'detail_commenter' => 'Autor del comentario:', + 'detail_comment' => 'Comentario:', + 'detail_created_by' => 'Creado por:', + 'detail_updated_by' => 'Actualizado por:', + + 'action_view_comment' => 'Ver comentario', + 'action_view_page' => 'Ver página', + + 'footer_reason' => 'Esta notificación fue enviada porque :link cubre este tipo de actividad para este artículo.', + 'footer_reason_link' => 'sus preferencias de notificación', +]; diff --git a/lang/es/preferences.php b/lang/es/preferences.php index 1ad40d802..d18be7528 100644 --- a/lang/es/preferences.php +++ b/lang/es/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferencias', + 'shortcuts' => 'Accesos directos', 'shortcuts_interface' => 'Accesos directos de la interfaz', 'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos directos de la interfaz, utilizados para la navegación y las acciones.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Guardar accesos directos', 'shortcuts_overlay_desc' => 'Nota: Cuando se activan los accesos directos se puede mostrar la ayuda presionando la tecla "?" que resaltará los accesos rápidos disponibles para las acciones actualmente visibles en la pantalla.', 'shortcuts_update_success' => '¡Las preferencias de accesos directos han sido actualizadas!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gestione los atajos de teclado que puede utilizar para navegar por la interfaz de usuario del sistema.', + + 'notifications' => 'Preferencias de notificaciones', + 'notifications_desc' => 'Controle las notificaciones por correo electrónico que recibe cuando se realiza cierta actividad dentro del sistema.', + 'notifications_opt_own_page_changes' => 'Notificar sobre los cambios en las páginas en las que soy propietario', + 'notifications_opt_own_page_comments' => 'Notificar sobre comentarios en las páginas en las que soy propietario', + 'notifications_opt_comment_replies' => 'Notificar sobre respuestas a mis comentarios', + 'notifications_save' => 'Guardar preferencias', + 'notifications_update_success' => '¡Se han actualizado las preferencias de notificaciones!', + 'notifications_watched' => 'Elementos vistos e ignorados', + 'notifications_watched_desc' => ' A continuación se muestran los elementos que tienen preferencias personalizadas de monitorización. Para actualizar sus preferencias, vea el artículo y las opciones se mostrarán en la barra lateral.', + + 'profile_overview_desc' => ' Gestione los detalles de su perfil de usuario, incluyendo las opciones de idioma y autenticación preferidas.', +]; diff --git a/lang/es/settings.php b/lang/es/settings.php index 4ffc7dd1e..1acebd7b3 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gestionar ajustes de la aplicación', 'role_export_content' => 'Exportar contenido', 'role_editor_change' => 'Cambiar editor de página', + 'role_notifications' => 'Recibir y gestionar notificaciones', 'role_asset' => 'Permisos de contenido', 'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario alterar sus propios privilegios o los privilegios de otros en el sistema. Sólo asignar roles con estos permisos a usuarios de confianza.', 'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los contenidos del sistema. Los permisos de Libros, Capítulos y Páginas sobreescribiran estos permisos.', diff --git a/lang/es_AR/activities.php b/lang/es_AR/activities.php index f54e0075a..760652e48 100644 --- a/lang/es_AR/activities.php +++ b/lang/es_AR/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '".name" se añadió a sus favoritos', 'favourite_remove_notification' => '".name" se eliminó de sus favoritos', + // Watching + 'watch_update_level_notification' => 'Preferencias de suscripciones actualizadas correctamente', + // Auth 'auth_login' => 'sesión iniciada', 'auth_register' => 'registrado como usuario nuevo', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restaurado desde la papelera de reciclaje', 'recycle_bin_destroy' => 'eliminado de la papelera de reciclaje', - // Other + // Comments 'commented_on' => 'comentado', + 'comment_create' => 'comentario añadido', + 'comment_update' => 'comentario actualizado', + 'comment_delete' => 'comentario borrado', + + // Other 'permissions_update' => 'permisos actualizados', ]; diff --git a/lang/es_AR/common.php b/lang/es_AR/common.php index a298a7a1d..aaec7c443 100644 --- a/lang/es_AR/common.php +++ b/lang/es_AR/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remover', 'add' => 'Agregar', 'configure' => 'Configurar', + 'manage' => 'Gestionar', 'fullscreen' => 'Pantalla completa', 'favourite' => 'Favoritos', 'unfavourite' => 'Eliminar de favoritos', diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index 4b7fb006b..bf4194b21 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permisos del Estante Actualizados', 'shelves_permissions_active' => 'Permisos Activos del Estante', 'shelves_permissions_cascade_warning' => 'Los permisos en los estantes no se aplican automáticamente a los libros que contengan. Esto se debe a que un libro puede existir en múltiples estantes. Sin embargo, los permisos pueden ser aplicados a los libros del estante utilizando la opción de abajo.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Los permisos de creación de estantes sólo se utilizan para copiar los permisos a los libros contenidos utilizando la acción a continuación. No controlan la capacidad de crear libros.', 'shelves_copy_permissions_to_books' => 'Copiar Permisos a los Libros', 'shelves_copy_permissions' => 'Copiar Permisos', 'shelves_copy_permissions_explain' => 'Esta acción aplicará los permisos del estante a todos los libros dentro del mismo. Antes de activarlo, asegúrese de que todos los cambios de permisos para este estante fueron guardados.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insertar Dibujo', 'pages_md_show_preview' => 'Mostrar vista previa', 'pages_md_sync_scroll' => 'Sincronizar desplazamiento de vista previa', + 'pages_drawing_unsaved' => 'Encontrado dibujo sin guardar', + 'pages_drawing_unsaved_confirm' => 'Se encontraron datos no guardados del dibujo de un intento de guardado fallido. ¿Desea restaurar y continuar editando el dibujo no guardado?', 'pages_not_in_chapter' => 'La página no esá en el capítulo', 'pages_move' => 'Mover página', 'pages_copy' => 'Copiar página', @@ -403,4 +405,28 @@ return [ 'references' => 'Referencias', 'references_none' => 'No hay referencias a este elemento.', 'references_to_desc' => 'A continuación se muestran todas las páginas en el sistema que tienen un enlace a este elemento.', + + // Watch Options + 'watch' => 'Suscribirme', + 'watch_title_default' => 'Preferencias por defecto', + 'watch_desc_default' => 'Revertir suscripciones a tus preferencias de notificación por defecto.', + 'watch_title_ignore' => 'Ignorar', + 'watch_desc_ignore' => 'Ignorar todas las notificaciones, incluyendo las de las preferencias a nivel de usuario.', + 'watch_title_new' => 'Nuevas páginas', + 'watch_desc_new' => 'Notificar cuando se crea una nueva página dentro de este elemento.', + 'watch_title_updates' => 'Todas las actualizaciones de páginas', + 'watch_desc_updates' => 'Notificar todos los cambios de páginas y páginas nuevas.', + 'watch_desc_updates_page' => 'Notificar todos los cambios en la página.', + 'watch_title_comments' => 'Todas las actualizaciones de páginas y comentarios', + 'watch_desc_comments' => 'Notificar sobre todas las páginas nuevas, cambios de página y nuevos comentarios.', + 'watch_desc_comments_page' => 'Notificar los cambios en las páginas y los nuevos comentarios.', + 'watch_change_default' => 'Cambiar preferencias de notificación por defecto', + 'watch_detail_ignore' => 'Ignorar notificaciones', + 'watch_detail_new' => 'Suscripciones de nuevas páginas', + 'watch_detail_updates' => 'Suscripciones de nuevas páginas y actualizaciones de páginas', + 'watch_detail_comments' => 'Suscripciones de nuevas páginas, actualizaciones de páginas y comentarios', + 'watch_detail_parent_book' => 'Subscripciones por libro contenedor', + 'watch_detail_parent_book_ignore' => 'Ignorando a través del libro contenedor', + 'watch_detail_parent_chapter' => 'Subscripciones por capítulo superior', + 'watch_detail_parent_chapter_ignore' => 'Ignorar por capítulo superior', ]; diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index fce1913c6..e8365981d 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', + // HTTP errors + 'http_ssr_url_no_match' => 'La URL no coincide con los hosts SSR permitidos', ]; diff --git a/lang/es_AR/notifications.php b/lang/es_AR/notifications.php new file mode 100644 index 000000000..4e76a5924 --- /dev/null +++ b/lang/es_AR/notifications.php @@ -0,0 +1,26 @@ + 'Nuevo comentario en la página: :pageName', + 'new_comment_intro' => 'Un usuario ha comentado en una página de :appName:', + 'new_page_subject' => 'Nueva página: :pageName', + 'new_page_intro' => 'Una nueva página ha sido creada en :appName:', + 'updated_page_subject' => 'Página actualizada: :pageName', + 'updated_page_intro' => 'Una página ha sido actualizada en :appName:', + 'updated_page_debounce' => 'Para prevenir notificaciones en masa, durante un tiempo no se enviarán notificaciones para futuras ediciones de esta página por el mismo editor.', + + 'detail_page_name' => 'Nombre de página:', + 'detail_commenter' => 'Autor del comentario:', + 'detail_comment' => 'Comentario:', + 'detail_created_by' => 'Creado por:', + 'detail_updated_by' => 'Actualizado por:', + + 'action_view_comment' => 'Ver comentario', + 'action_view_page' => 'Ver página', + + 'footer_reason' => 'Esta notificación fue enviada porque :link cubre este tipo de actividad para este artículo.', + 'footer_reason_link' => 'sus preferencias de notificación', +]; diff --git a/lang/es_AR/preferences.php b/lang/es_AR/preferences.php index 6c1245449..4ad955d6d 100644 --- a/lang/es_AR/preferences.php +++ b/lang/es_AR/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferencias', + 'shortcuts' => 'Atajos', 'shortcuts_interface' => 'Atajos del Teclado para la Interfaz', 'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos rápidos de la interfaz, utilizados para la navegación y las acciones.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Guardar atajos', 'shortcuts_overlay_desc' => 'Nota: Cuando se activan los atajos de teclado se puede visualizar la ayuda presionando la tecla "?", que resaltará los atajos disponibles para las acciones visibles actualmente en la pantalla.', 'shortcuts_update_success' => '¡Se actualizaron las preferencias de atajos de teclado!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gestione los atajos de teclado que puede utilizar para navegar por la interfaz de usuario del sistema.', + + 'notifications' => 'Preferencias de notificaciones', + 'notifications_desc' => 'Controle las notificaciones por correo electrónico que recibe cuando se realiza cierta actividad dentro del sistema.', + 'notifications_opt_own_page_changes' => 'Notificar sobre los cambios en las páginas en las que soy propietario', + 'notifications_opt_own_page_comments' => 'Notificar sobre comentarios en las páginas en las que soy propietario', + 'notifications_opt_comment_replies' => 'Notificar sobre respuestas a mis comentarios', + 'notifications_save' => 'Guardar preferencias', + 'notifications_update_success' => '¡Se han actualizado las preferencias de notificaciones!', + 'notifications_watched' => 'Elementos vistos e ignorados', + 'notifications_watched_desc' => ' A continuación se muestran los elementos que tienen preferencias personalizadas de monitorización. Para actualizar sus preferencias, vea el artículo y las opciones se mostrarán en la barra lateral.', + + 'profile_overview_desc' => ' Gestione los detalles de su perfil de usuario, incluyendo las opciones de idioma y autenticación preferidas.', +]; diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index a9a9ac9cc..67895ffeb 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -164,6 +164,7 @@ return [ 'role_manage_settings' => 'Gestionar ajustes de activos', 'role_export_content' => 'Exportar contenido', 'role_editor_change' => 'Cambiar editor de página', + 'role_notifications' => 'Recibir y gestionar notificaciones', 'role_asset' => 'Permisos de activos', 'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario modificar sus propios privilegios o los privilegios de otros usuarios en el sistema. Asignar roles con estos permisos sólo a usuarios de comfianza.', 'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los activos del sistema. Permisos definidos en Libros, Capítulos y Páginas ignorarán estos permisos.', diff --git a/lang/et/activities.php b/lang/et/activities.php index 453e630bc..468125510 100644 --- a/lang/et/activities.php +++ b/lang/et/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" lisati su lemmikute hulka', 'favourite_remove_notification' => '":name" eemaldati su lemmikute hulgast', + // Watching + 'watch_update_level_notification' => 'Jälgimise eelistused edukalt salvestatud', + // Auth 'auth_login' => 'logis sisse', 'auth_register' => 'registreerus uue kasutajana', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'taastas prügikastist', 'recycle_bin_destroy' => 'eemaldas prügikastist', - // Other + // Comments 'commented_on' => 'kommenteeris lehte', + 'comment_create' => 'lisas kommentaari', + 'comment_update' => 'muutis kommentaari', + 'comment_delete' => 'kustutas kommentaari', + + // Other 'permissions_update' => 'muutis õiguseid', ]; diff --git a/lang/et/common.php b/lang/et/common.php index 41a5ca39b..61fc259b2 100644 --- a/lang/et/common.php +++ b/lang/et/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Eemalda', 'add' => 'Lisa', 'configure' => 'Seadista', + 'manage' => 'Halda', 'fullscreen' => 'Täisekraan', 'favourite' => 'Lemmik', 'unfavourite' => 'Eemalda lemmik', diff --git a/lang/et/entities.php b/lang/et/entities.php index f47e46ea8..2c42b0512 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -214,7 +214,7 @@ return [ 'pages_editing_page' => 'Lehe muutmine', 'pages_edit_draft_save_at' => 'Mustand salvestatud ', 'pages_edit_delete_draft' => 'Kustuta mustand', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => 'Kas oled kindel, et soovid mustandi muudatused kustutada? Kõik viimasest salvestamisest saadik tehtud muudatused kaovad ning redaktorisse laetakse viimati salvestatud seis.', 'pages_edit_discard_draft' => 'Loobu mustandist', 'pages_edit_switch_to_markdown' => 'Kasuta Markdown redaktorit', 'pages_edit_switch_to_markdown_clean' => '(Puhas sisu)', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Lisa joonis', 'pages_md_show_preview' => 'Näita eelvaadet', 'pages_md_sync_scroll' => 'Sünkrooni eelvaate kerimine', + 'pages_drawing_unsaved' => 'Leiti salvestamata joonis', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Leht ei kuulu peatüki alla', 'pages_move' => 'Liiguta leht', 'pages_copy' => 'Kopeeri leht', @@ -266,9 +268,9 @@ return [ 'pages_revisions_restore' => 'Taasta', 'pages_revisions_none' => 'Sellel lehel ei ole redaktsioone', 'pages_copy_link' => 'Kopeeri link', - 'pages_edit_content_link' => 'Jump to section in editor', + 'pages_edit_content_link' => 'Hüppa redaktoris sektsioonini', 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', + 'pages_pointer_label' => 'Lehe sektsiooni valikud', 'pages_pointer_permalink' => 'Page Section Permalink', 'pages_pointer_include_tag' => 'Page Section Include Tag', 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', @@ -287,8 +289,8 @@ return [ 'time_b' => 'viimase :minCount minuti jooksul', 'message' => ':start :time. Ärge teineteise muudatusi üle kirjutage!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'Mustand ära visatud! Redaktorisse laeti lehe värske sisu', + 'pages_draft_deleted' => 'Mustand kustutatud! Redaktorisse laeti lehe värske sisu', 'pages_specific' => 'Spetsiifiline leht', 'pages_is_template' => 'Lehe mall', @@ -366,7 +368,7 @@ return [ 'comment_new' => 'Uus kommentaar', 'comment_created' => 'kommenteeris :createDiff', 'comment_updated' => 'Muudetud :updateDiff :username poolt', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Uuendatud', 'comment_deleted_success' => 'Kommentaar kustutatud', 'comment_created_success' => 'Kommentaar lisatud', 'comment_updated_success' => 'Kommentaar muudetud', @@ -403,4 +405,28 @@ return [ 'references' => 'Viited', 'references_none' => 'Sellele objektile ei ole viiteid.', 'references_to_desc' => 'Allpool on kõik teadaolevad lehed, mis sellele objektile viitavad.', + + // Watch Options + 'watch' => 'Jälgi', + 'watch_title_default' => 'Vaikimisi eelistused', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignoreeri', + 'watch_desc_ignore' => 'Ignoreeri kõiki teavitusi, ka kasutaja tasemel määratud eelistusi.', + 'watch_title_new' => 'Uued lehed', + 'watch_desc_new' => 'Teavita, kui sellesse objekti lisatakse uus leht.', + 'watch_title_updates' => 'Kõik lehed', + 'watch_desc_updates' => 'Teavita kõigist uutest lehtedest ja lehtede muudatustest.', + 'watch_desc_updates_page' => 'Teavita kõigist lehtede muudatustest.', + 'watch_title_comments' => 'Kõik lehed ja kommentaarid', + 'watch_desc_comments' => 'Teavita kõigist uutest lehtedest, lehtede muudatustest ja uutest kommentaaridest.', + 'watch_desc_comments_page' => 'Teavita lehtede muudatustest ja uutest kommentaaridest.', + 'watch_change_default' => 'Muuda vaikimisi teavituste eelistusi', + 'watch_detail_ignore' => 'Teavitusi ignoreeritakse', + 'watch_detail_new' => 'Jälgitakse uusi lehti', + 'watch_detail_updates' => 'Jälgitakse uusi lehti ja muudatusi', + 'watch_detail_comments' => 'Jälgitakse uusi lehti, muudatusi ja kommentaare', + 'watch_detail_parent_book' => 'Jälgitakse raamatu kaudu', + 'watch_detail_parent_book_ignore' => 'Ignoreeritakse raamatu kaudu', + 'watch_detail_parent_chapter' => 'Jälgitakse peatüki kaudu', + 'watch_detail_parent_chapter_ignore' => 'Ignoreeritakse peatüki kaudu', ]; diff --git a/lang/et/errors.php b/lang/et/errors.php index e7b45bfa9..fd68b12af 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Test e-kirja saatmisel tekkis viga:', + // HTTP errors + 'http_ssr_url_no_match' => 'URL ei klapi ühegi lubatud SSR hostiga', ]; diff --git a/lang/et/notifications.php b/lang/et/notifications.php new file mode 100644 index 000000000..6a2c2d8bf --- /dev/null +++ b/lang/et/notifications.php @@ -0,0 +1,26 @@ + 'Uus kommentaar lehel: :pageName', + 'new_comment_intro' => 'Rakenduses :appName kommenteeriti lehte:', + 'new_page_subject' => 'Uus leht: :pageName', + 'new_page_intro' => 'Rakenduses :appName lisati uus leht:', + 'updated_page_subject' => 'Muudetud leht: :pageName', + 'updated_page_intro' => 'Rakenduses :appName muudeti lehte:', + 'updated_page_debounce' => 'Et vältida liigseid teavitusi, ei saadeta sulle mõnda aega teavitusi selle lehe muutmiste kohta sama kasutaja poolt.', + + 'detail_page_name' => 'Lehe nimetus:', + 'detail_commenter' => 'Kommenteerija:', + 'detail_comment' => 'Kommentaar:', + 'detail_created_by' => 'Autor:', + 'detail_updated_by' => 'Muutja:', + + 'action_view_comment' => 'Vaata kommentaari', + 'action_view_page' => 'Vaata lehte', + + 'footer_reason' => 'See teavitus saadeti sulle, sest :link sisaldavad selle objekti kohta sellist tegevust.', + 'footer_reason_link' => 'sinu teavituste eelistused', +]; diff --git a/lang/et/preferences.php b/lang/et/preferences.php index 05ba03271..dbe80a0be 100644 --- a/lang/et/preferences.php +++ b/lang/et/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Eelistused', + 'shortcuts' => 'Kiirklahvid', 'shortcuts_interface' => 'Kasutajaliidese kiirklahvid', 'shortcuts_toggle_desc' => 'Siit saad sisse ja välja lülitada navigeerimiseks ja tegevusteks kasutatavad kiirklahvid.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Salvesta kiirklahvid', 'shortcuts_overlay_desc' => 'Märkus: Kui kiirklahvid on sisse lülitatud, saab "?" vajutades kuvada abiinfo, mis märgib ära kõigi hetkel ekraanil nähtavate tegevuste kiirklahvid.', 'shortcuts_update_success' => 'Kiirklahvide eelistused on salvestatud!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Halda klaviatuuri kiirklahve süsteemi kasutajaliideses navigeerimiseks.', + + 'notifications' => 'Teavituste eelistused', + 'notifications_desc' => 'Halda e-posti teavitusi, mis saadetakse teatud tegevuste puhul.', + 'notifications_opt_own_page_changes' => 'Teavita muudatustest minu lehtedel', + 'notifications_opt_own_page_comments' => 'Teavita kommentaaridest minu lehtedel', + 'notifications_opt_comment_replies' => 'Teavita vastustest minu kommentaaridele', + 'notifications_save' => 'Salvesta eelistused', + 'notifications_update_success' => 'Teavituste eelistused on salvestatud!', + 'notifications_watched' => 'Jälgitud ja ignoreeritud objektid', + 'notifications_watched_desc' => ' Allpool on objektid, millele on määratud kohaldatud jälgimise eelistused. Eelistuste muutmiseks ava vastav objekt ning leia jälgimise valikud külgmenüüs.', + + 'profile_overview_desc' => ' Halda oma kasutajaprofiili andmeid, kaasa arvatud keele eelistust ja autentimisvalikuid.', +]; diff --git a/lang/et/settings.php b/lang/et/settings.php index 2e00daa77..930dab580 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Rakenduse seadete haldamine', 'role_export_content' => 'Sisu eksport', 'role_editor_change' => 'Lehe redaktori muutmine', + 'role_notifications' => 'Võta vastu ja halda teavitusi', 'role_asset' => 'Sisu õigused', 'roles_system_warning' => 'Pane tähele, et ülalolevad kolm õigust võimaldavad kasutajal enda või teiste kasutajate õiguseid muuta. Määra nende õigustega roll ainult usaldusväärsetele kasutajatele.', 'role_asset_desc' => 'Need load kontrollivad vaikimisi ligipääsu süsteemis olevale sisule. Raamatute, peatükkide ja lehtede õigused rakenduvad esmajärjekorras.', diff --git a/lang/eu/activities.php b/lang/eu/activities.php index afdef0d4c..2e9f586a8 100644 --- a/lang/eu/activities.php +++ b/lang/eu/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" zure gogoetara gehitua izan da', 'favourite_remove_notification' => '":name" zure gogokoetatik ezabatua izan da', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'iruzkinak', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'eguneratu baimenak', ]; diff --git a/lang/eu/common.php b/lang/eu/common.php index c43039ca5..ca707702e 100644 --- a/lang/eu/common.php +++ b/lang/eu/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Ezabatu', 'add' => 'Gehitu', 'configure' => 'Konfiguratu', + 'manage' => 'Manage', 'fullscreen' => 'Pantaila osoa', 'favourite' => 'Gogokoa', 'unfavourite' => 'Desatsegina', diff --git a/lang/eu/entities.php b/lang/eu/entities.php index d5ab5c9ea..dfae52af1 100644 --- a/lang/eu/entities.php +++ b/lang/eu/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Txertatu marrazki berria', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/eu/errors.php b/lang/eu/errors.php index 289785a36..d71cf742c 100644 --- a/lang/eu/errors.php +++ b/lang/eu/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/eu/notifications.php b/lang/eu/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/eu/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/eu/preferences.php b/lang/eu/preferences.php index c3aa8a3ba..3d37f6e3a 100644 --- a/lang/eu/preferences.php +++ b/lang/eu/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Lastertekla', 'shortcuts_interface' => 'Teklatuko lasterbideak ikusi', 'shortcuts_toggle_desc' => 'Hemen, nabigaziorako eta ekintzetarako erabiltzen diren teklatu-sistemako lasterbideak gaitu edo desgaitu daitezke.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Gorde lasterbideak', 'shortcuts_overlay_desc' => 'Oharra: Lasterbideak gaituta daudenean, "?" sakagailuaren bidez laguntzaileen gainjartze bat egongo da, eta horrek pantailan gaur egun ikus daitezkeen ekintzetarako dauden lasterbideak nabarmenduko ditu.', 'shortcuts_update_success' => 'Zure lehentasunak gorde dira!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/eu/settings.php b/lang/eu/settings.php index f8d255897..93137326a 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Kudeatu aplikazio ezarpenak', 'role_export_content' => 'Exportatu edukia', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Fitxategi baimenak', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', diff --git a/lang/fa/activities.php b/lang/fa/activities.php index 453a6cbd7..ea4e18142 100644 --- a/lang/fa/activities.php +++ b/lang/fa/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'بازیابی صفحه', 'page_restore_notification' => 'صفحه با موفقیت بازیابی شد', 'page_move' => 'انتقال صفحه', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'صفحه با موفقیت جابه‌جا شد', // Chapters 'chapter_create' => 'ایجاد فصل', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'حذف فصل', 'chapter_delete_notification' => 'فصل با موفقیت حذف شد', 'chapter_move' => 'انتقال فصل', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'فصل با موفقیت جابه‌جا شد', // Books 'book_create' => 'ایجاد کتاب', @@ -52,12 +52,15 @@ return [ // Revisions 'revision_restore' => 'restored revision', 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_delete_notification' => 'نسخه مورد نظر با موفقیت حذف شد', // Favourites 'favourite_add_notification' => '":name" به علاقه مندی های شما اضافه شد', 'favourite_remove_notification' => '":name" از علاقه مندی های شما حذف شد', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -70,7 +73,7 @@ return [ // Settings 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', + 'settings_update_notification' => 'تنظیمات با موفقیت به روز شد', 'maintenance_action_run' => 'ran maintenance action', // Webhooks @@ -83,7 +86,7 @@ return [ // Users 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', + 'user_create_notification' => 'کاربر با موفقیت به ایجاد شد', 'user_update' => 'updated user', 'user_update_notification' => 'کاربر با موفقیت به روز شد', 'user_delete' => 'deleted user', @@ -107,10 +110,15 @@ return [ // Recycle Bin 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_restore' => 'از سطل بازیافت، بازآوری شده است', + 'recycle_bin_destroy' => 'از سطل بازیافت حذف شده است', + + // Comments + 'commented_on' => 'ثبت دیدگاه', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', // Other - 'commented_on' => 'ثبت دیدگاه', 'permissions_update' => 'به روزرسانی مجوزها', ]; diff --git a/lang/fa/common.php b/lang/fa/common.php index a3bb039ab..f609c528f 100644 --- a/lang/fa/common.php +++ b/lang/fa/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'لغو', - 'close' => 'Close', + 'close' => 'خروج', 'confirm' => 'تایید', 'back' => 'بازگشت', 'save' => 'ذخیره', @@ -42,6 +42,7 @@ return [ 'remove' => 'حذف', 'add' => 'ﺍﻓﺰﻭﺩﻥ', 'configure' => 'پیکربندی کنید', + 'manage' => 'Manage', 'fullscreen' => 'تمام صفحه', 'favourite' => 'علاقه‌مندی', 'unfavourite' => 'حذف از علاقه‌مندی', @@ -65,7 +66,7 @@ return [ // Misc 'deleted_user' => 'کاربر حذف شده', 'no_activity' => 'بایگانی برای نمایش وجود ندارد', - 'no_items' => 'هیچ آیتمی موجود نیست', + 'no_items' => 'هیچ موردی در دسترس نیست', 'back_to_top' => 'بازگشت به بالا', 'skip_to_main_content' => 'رفتن به محتوای اصلی', 'toggle_details' => 'معکوس کردن اطلاعات', diff --git a/lang/fa/components.php b/lang/fa/components.php index f041e9e19..d4e77bebf 100644 --- a/lang/fa/components.php +++ b/lang/fa/components.php @@ -6,34 +6,34 @@ return [ // Image Manager 'image_select' => 'انتخاب تصویر', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_list' => 'لیست تصاویر', + 'image_details' => 'جزئیات تصویر', + 'image_upload' => 'بارگذاری تصویر', + 'image_intro' => 'در اینجا می توانید تصاویری که قبلاً در سیستم آپلود شده اند را انتخاب و مدیریت کنید.', + 'image_intro_upload' => 'با کشیدن یک فایل تصویری به این پنجره یا با استفاده از دکمه "بارگذاری تصویر" در بالا، یک تصویر جدید آپلود کنید.', 'image_all' => 'همه', 'image_all_title' => 'نمایش تمام تصاویر', 'image_book_title' => 'تصاویر بارگذاری شده در این کتاب را مشاهده کنید', 'image_page_title' => 'تصاویر بارگذاری شده در این صفحه را مشاهده کنید', 'image_search_hint' => 'جستجو بر اساس نام تصویر', 'image_uploaded' => 'بارگذاری شده :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'بارگذاری شده توسط:userName', + 'image_uploaded_to' => 'بارگذاری شده در صفحه:pageLink', + 'image_updated' => 'به‌روزرسانی شده در:updateDate', 'image_load_more' => 'بارگذاری بیشتر', 'image_image_name' => 'نام تصویر', 'image_delete_used' => 'این تصویر در صفحات زیر استفاده شده است.', 'image_delete_confirm_text' => 'آیا مطمئن هستید که میخواهید این عکس را پاک کنید؟', 'image_select_image' => 'انتخاب تصویر', 'image_dropzone' => 'تصاویر را رها کنید یا برای بارگذاری اینجا را کلیک کنید', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => 'تصویر را برای بارگذاری به اینجا بکشید و رها کنید', 'images_deleted' => 'تصاویر حذف شده', 'image_preview' => 'پیش نمایش تصویر', 'image_upload_success' => 'تصویر با موفقیت بارگذاری شد', 'image_update_success' => 'جزئیات تصویر با موفقیت به روز شد', 'image_delete_success' => 'تصویر با موفقیت حذف شد', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', + 'image_replace' => 'جایگزینی تصویر', + 'image_replace_success' => 'تصویر با موفقیت به روز شد', // Code Editor 'code_editor' => 'ویرایش کد', diff --git a/lang/fa/entities.php b/lang/fa/entities.php index 1f0ad28e9..d945b8370 100644 --- a/lang/fa/entities.php +++ b/lang/fa/entities.php @@ -63,8 +63,8 @@ return [ 'search_terms' => 'عبارات جستجو', 'search_content_type' => 'نوع محتوا', 'search_exact_matches' => 'مطابقت کامل', - 'search_tags' => 'جستجوها را برچسب بزنید', - 'search_options' => 'گزینه ها', + 'search_tags' => 'جستجو در برچسب‌ها', + 'search_options' => 'گزینه‌ها', 'search_viewed_by_me' => 'بازدید شده به وسیله من', 'search_not_viewed_by_me' => 'توسط من مشاهده نشده است', 'search_permissions_set' => 'مجوزها تنظیم شده است', @@ -81,12 +81,12 @@ return [ // Shelves 'shelf' => 'قفسه', - 'shelves' => 'قفسه ها', + 'shelves' => 'قفسه‌ها', 'x_shelves' => ':count قفسه|:count قفسه‌ها', 'shelves_empty' => 'هیچ قفسه ای ایجاد نشده است', 'shelves_create' => 'ایجاد قفسه جدید', - 'shelves_popular' => 'قفسه های محبوب', - 'shelves_new' => 'قفسه های جدید', + 'shelves_popular' => 'قفسه‌های محبوب', + 'shelves_new' => 'قفسه‌های جدید', 'shelves_new_action' => 'قفسه جدید', 'shelves_popular_empty' => 'محبوب ترین قفسه ها در اینجا ظاهر می شوند.', 'shelves_new_empty' => 'جدیدترین قفسه های ایجاد شده در اینجا ظاهر می شوند.', @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'مجوزهای کانال بروزرسانی شد', 'shelves_permissions_active' => 'مجوزهای قفسه فعال است', 'shelves_permissions_cascade_warning' => 'مجوزهای موجود در قفسه‌ها به طور خودکار به کتاب‌های حاوی اطلاق نمی‌شوند. دلیل آن این است که یک کتاب می تواند در چندین قفسه وجود داشته باشد. با این حال، مجوزها را می‌توان با استفاده از گزینه پایین همین صفحه در کتاب‌های فرزند کپی کرد.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'مجوزهای «ایجاد» قفسه فقط برای کپی کردن مجوزها در کتاب‌های کودک با استفاده از عملکرد زیر استفاده می‌شوند. آنها توانایی ایجاد کتاب را کنترل نمی‌کنند.', 'shelves_copy_permissions_to_books' => 'کپی مجوزها در کتابها', 'shelves_copy_permissions' => 'کپی مجوزها', 'shelves_copy_permissions_explain' => 'با این کار تنظیمات مجوز فعلی این قفسه برای همه کتاب‌های موجود در آن اعمال می‌شود. قبل از فعال کردن، مطمئن شوید که هر گونه تغییر در مجوزهای این قفسه، ذخیره شده است.', @@ -114,7 +114,7 @@ return [ // Books 'book' => 'کتاب', - 'books' => 'کتابها', + 'books' => 'کتاب‌ها', 'x_books' => ':count کتاب|:count کتاب', 'books_empty' => 'هیچ کتابی ایجاد نشده است', 'books_popular' => 'کتاب های محبوب', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'درج طرح', 'pages_md_show_preview' => 'دیدن پیش نمایش', 'pages_md_sync_scroll' => 'هماهنگ سازی اسکرول پیش نمایش', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'صفحه در یک فصل نیست', 'pages_move' => 'انتقال صفحه', 'pages_copy' => 'کپی صفحه', @@ -320,10 +322,10 @@ return [ 'attachments_explain_instant_save' => 'تغییرات در اینجا فورا ذخیره می شوند.', 'attachments_upload' => 'آپلود فایل', 'attachments_link' => 'پیوند را ضمیمه کنید', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', + 'attachments_upload_drop' => 'می‌توانید فایلی را در اینجا بکشید و رها کنید تا آن را به عنوان پیوست آپلود کنید.', 'attachments_set_link' => 'پیوند را تنظیم کنید', 'attachments_delete' => 'آیا مطمئن هستید که می خواهید این پیوست را حذف کنید؟', - 'attachments_dropzone' => 'Drop files here to upload', + 'attachments_dropzone' => 'فایل را برای بارگذاری به اینجا بکشید و رها کنید', 'attachments_no_files' => 'هیچ فایلی آپلود نشده است', 'attachments_explain_link' => 'اگر ترجیح می دهید فایلی را آپلود نکنید، می توانید پیوندی را پیوست کنید. این می تواند پیوندی به صفحه دیگر یا پیوندی به فایلی در فضای ابری باشد.', 'attachments_link_name' => 'نام پیوند', @@ -372,7 +374,7 @@ return [ 'comment_updated_success' => 'نظر به روز شد', 'comment_delete_confirm' => 'آیا مطمئن هستید که می خواهید این نظر را حذف کنید؟', 'comment_in_reply_to' => 'در پاسخ به :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'در اینجا نظراتی که در این صفحه گذاشته شده است، مشاهده می‌شود. نظرات را می‌توان در هنگام مشاهده صفحه ذخیره شده، اضافه و مدیریت کرد.', // Revision 'revision_delete_confirm' => 'آیا مطمئن هستید که می خواهید این ویرایش را حذف کنید؟', @@ -403,4 +405,28 @@ return [ 'references' => 'مراجع', 'references_none' => 'هیچ رفرنسی برای این قلم یافت نشد.', 'references_to_desc' => 'در زیر تمام صفحات شناخته شده در سیستم که به این مورد پیوند دارند، نشان داده شده است.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/fa/errors.php b/lang/fa/errors.php index fed47cf56..ad3772b0c 100644 --- a/lang/fa/errors.php +++ b/lang/fa/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'خطا در هنگام ارسال ایمیل آزمایشی:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/fa/notifications.php b/lang/fa/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/fa/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/fa/preferences.php b/lang/fa/preferences.php index d0f1598e1..187f82ef8 100644 --- a/lang/fa/preferences.php +++ b/lang/fa/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'میانبرها', 'shortcuts_interface' => 'میانبرهای صفحه کلید', 'shortcuts_toggle_desc' => 'در اینجا می توانید میانبرهای سیستم را که برای پیمایش و ... استفاده می شود، فعال یا غیرفعال کنید.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'ذخیره کلیدهای میانبر', 'shortcuts_overlay_desc' => 'توجه: هنگامی که میانبرها فعال هستند، یک رابط کمکی با فشار دادن "؟" در دسترس است که میانبرهای موجود برای اقداماتی که در حال حاضر روی صفحه قابل مشاهده است را برجسته می‌کند.', 'shortcuts_update_success' => 'تنظیمات میانبر به روز شده است!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 7afbe11fc..0a9d531a1 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'تنظیمات برنامه را مدیریت کنید', 'role_export_content' => 'صادرات محتوا', 'role_editor_change' => 'تغییر ویرایشگر صفحه', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'مجوزهای دارایی', 'roles_system_warning' => 'توجه داشته باشید که دسترسی به هر یک از سه مجوز فوق می‌تواند به کاربر اجازه دهد تا امتیازات خود یا امتیازات دیگران را در سیستم تغییر دهد. فقط نقش هایی را با این مجوزها به کاربران مورد اعتماد اختصاص دهید.', 'role_asset_desc' => 'این مجوزها دسترسی پیش‌فرض به دارایی‌های درون سیستم را کنترل می‌کنند. مجوزهای مربوط به کتاب‌ها، فصل‌ها و صفحات این مجوزها را لغو می‌کنند.', diff --git a/lang/fr/activities.php b/lang/fr/activities.php index 8baf3d385..8edbab4b8 100644 --- a/lang/fr/activities.php +++ b/lang/fr/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" a été ajouté dans vos favoris', 'favourite_remove_notification' => '":name" a été supprimé de vos favoris', + // Watching + 'watch_update_level_notification' => 'Suivre les préférences mises à jour avec succès', + // Auth 'auth_login' => 'connecté', 'auth_register' => 'enregistré en tant que nouvel utilisateur', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restauré à partir de la corbeille', 'recycle_bin_destroy' => 'supprimé de la corbeille', - // Other + // Comments 'commented_on' => 'a commenté', + 'comment_create' => 'Commentaire ajouté', + 'comment_update' => 'Commentaire mis à jour', + 'comment_delete' => 'Commentaire supprimé', + + // Other 'permissions_update' => 'a mis à jour les autorisations sur', ]; diff --git a/lang/fr/common.php b/lang/fr/common.php index a7b7cd94d..fdb7c0dae 100644 --- a/lang/fr/common.php +++ b/lang/fr/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Enlever', 'add' => 'Ajouter', 'configure' => 'Configurer', + 'manage' => 'Gérer', 'fullscreen' => 'Plein écran', 'favourite' => 'Favoris', 'unfavourite' => 'Supprimer des favoris', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index b12c4b348..673ee1a2a 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permissions de l\'étagère mises à jour', 'shelves_permissions_active' => 'Permissions de l\'étagère activées', 'shelves_permissions_cascade_warning' => 'Les permissions sur les étagères ne sont pas automatiquement recopiées aux livres qu\'elles contiennent, car un livre peut exister dans plusieurs étagères. Les permissions peuvent cependant être recopiées vers les livres contenus en utilisant l\'option ci-dessous.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Les permissions de création d\'une étagère sont uniquement utilisées pour copier les permissions vers les livres enfants en utilisant l\'action ci-dessous. Elles ne contrôlent pas la possibilité de créer des livres.', 'shelves_copy_permissions_to_books' => 'Copier les permissions vers les livres', 'shelves_copy_permissions' => 'Copier les permissions', 'shelves_copy_permissions_explain' => 'Ceci va appliquer les permissions actuelles de cette étagère à tous les livres qu\'elle contient. Avant de continuer, assurez-vous que toutes les permissions de cette étagère ont été sauvegardées.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insérer un dessin', 'pages_md_show_preview' => 'Prévisualisation', 'pages_md_sync_scroll' => 'Défilement prévisualisation', + 'pages_drawing_unsaved' => 'Dessin non enregistré trouvé', + 'pages_drawing_unsaved_confirm' => 'Des données de dessin non enregistrées ont été trouvées à partir d\'une tentative de sauvegarde de dessin échouée. Voulez-vous restaurer et continuer à modifier ce dessin non sauvegardé ?', 'pages_not_in_chapter' => 'La page n\'est pas dans un chapitre', 'pages_move' => 'Déplacer la page', 'pages_copy' => 'Copier la page', @@ -403,4 +405,28 @@ return [ 'references' => 'Références', 'references_none' => 'Il n\'y a pas de références suivies à cet élément.', 'references_to_desc' => 'Vous trouverez ci-dessous toutes les pages connues du système qui ont un lien vers cet élément.', + + // Watch Options + 'watch' => 'Suivre', + 'watch_title_default' => 'Préférences par défaut', + 'watch_desc_default' => 'Revenir à vos préférences de notification par défaut.', + 'watch_title_ignore' => 'Ignorer', + 'watch_desc_ignore' => 'Ignorer toutes les notifications, y compris celles des préférences de niveau utilisateur.', + 'watch_title_new' => 'Nouvelles Pages', + 'watch_desc_new' => 'Notifier quand une nouvelle page est créée dans cet élément.', + 'watch_title_updates' => 'Toutes les mises à jour de page', + 'watch_desc_updates' => 'Notifier toutes les nouvelles pages et les changements de page.', + 'watch_desc_updates_page' => 'Notifier lors de toutes les modifications de page.', + 'watch_title_comments' => 'Toutes les mises à jour et commentaires de page', + 'watch_desc_comments' => 'Notifier toutes les nouvelles pages, les changements de page et les nouveaux commentaires.', + 'watch_desc_comments_page' => 'Notifier les changements de page et les nouveaux commentaires.', + 'watch_change_default' => 'Modifier les préférences de notification par défaut', + 'watch_detail_ignore' => 'Ignorer les notifications', + 'watch_detail_new' => 'Suivre les nouvelles pages', + 'watch_detail_updates' => 'Suivre les nouvelles pages et mises à jour', + 'watch_detail_comments' => 'Suivre les nouvelles pages, mises à jour et commentaires', + 'watch_detail_parent_book' => 'Suivre via le livre parent', + 'watch_detail_parent_book_ignore' => 'Ignorer via le livre parent', + 'watch_detail_parent_chapter' => 'Suivre via le chapitre parent', + 'watch_detail_parent_chapter_ignore' => 'Ignorer via le chapitre parent', ]; diff --git a/lang/fr/errors.php b/lang/fr/errors.php index 4c2700d16..03db2e95a 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Erreur émise lors de l\'envoi d\'un e-mail de test :', + // HTTP errors + 'http_ssr_url_no_match' => 'L\'URL ne correspond pas aux hôtes SSR autorisés configurés', ]; diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php new file mode 100644 index 000000000..37b40882d --- /dev/null +++ b/lang/fr/notifications.php @@ -0,0 +1,26 @@ + 'Nouveau commentaire sur la page : :pageName', + 'new_comment_intro' => 'Un utilisateur a commenté une page dans :appName:', + 'new_page_subject' => 'Nouvelle page : :pageName', + 'new_page_intro' => 'Une nouvelle page a été créée dans :appName:', + 'updated_page_subject' => 'Page mise à jour : :pageName', + 'updated_page_intro' => 'Une page a été mise à jour dans :appName:', + 'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.', + + 'detail_page_name' => 'Nom de la page :', + 'detail_commenter' => 'Commenta·teur·trice :', + 'detail_comment' => 'Commentaire :', + 'detail_created_by' => 'Créé par :', + 'detail_updated_by' => 'Mis à jour par :', + + 'action_view_comment' => 'Voir le commentaire', + 'action_view_page' => 'Afficher la page', + + 'footer_reason' => 'Cette notification vous a été envoyée car :link couvre ce type d\'activité pour cet élément.', + 'footer_reason_link' => 'vos préférences de notification', +]; diff --git a/lang/fr/preferences.php b/lang/fr/preferences.php index 4030ccbe6..3bc2fc2e9 100644 --- a/lang/fr/preferences.php +++ b/lang/fr/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Préférences', + 'shortcuts' => 'Raccourcis', 'shortcuts_interface' => 'Raccourcis clavier', 'shortcuts_toggle_desc' => 'Ici vous pouvez activer ou désactiver les raccourcis clavier, utilisés pour la navigation et les actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Sauvegarder les raccourcis', 'shortcuts_overlay_desc' => 'Note : Lorsque les raccourcis sont activés, assistant est disponible en appuyant sur "?" qui mettra en surbrillance les raccourcis disponibles pour les actions actuellement visibles à l\'écran.', 'shortcuts_update_success' => 'Les préférences de raccourci ont été mises à jour !', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gérer les raccourcis clavier que vous pouvez utiliser pour naviguer dans l\'interface utilisateur du système.', + + 'notifications' => 'Préférences de notification', + 'notifications_desc' => 'Contrôlez les notifications par e-mail que vous recevez lorsque certaines activités sont effectuées dans le système.', + 'notifications_opt_own_page_changes' => 'Notifier lors des modifications des pages que je possède', + 'notifications_opt_own_page_comments' => 'Notifier lorsque les pages que je possède sont commentées', + 'notifications_opt_comment_replies' => 'Notifier les réponses à mes commentaires', + 'notifications_save' => 'Enregistrer les préférences', + 'notifications_update_success' => 'Les préférences de notification ont été mises à jour !', + 'notifications_watched' => 'Éléments surveillés et ignorés', + 'notifications_watched_desc' => ' Voici les éléments qui ont des préférences de surveillance personnalisées appliquées. Pour mettre à jour vos préférences pour celles-ci, consultez l\'élément puis trouvez les options de surveillance dans la barre latérale.', + + 'profile_overview_desc' => ' Gérer les détails de votre profil utilisateur y compris la langue préférée et les options d\'authentification.', +]; diff --git a/lang/fr/settings.php b/lang/fr/settings.php index fb486301b..3a60a4476 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gérer les préférences de l\'application', 'role_export_content' => 'Exporter le contenu', 'role_editor_change' => 'Changer l\'éditeur de page', + 'role_notifications' => 'Recevoir et gérer les notifications', 'role_asset' => 'Permissions des ressources', 'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. N\'attribuez uniquement des rôles avec ces permissions qu\'à des utilisateurs de confiance.', 'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions', diff --git a/lang/he/activities.php b/lang/he/activities.php index 3bf0a4be3..1a21e4b04 100644 --- a/lang/he/activities.php +++ b/lang/he/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'הגיב/ה על', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'הרשאות עודכנו', ]; diff --git a/lang/he/common.php b/lang/he/common.php index b44d98028..253884004 100644 --- a/lang/he/common.php +++ b/lang/he/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'הסר', 'add' => 'הוסף', 'configure' => 'הגדרות', + 'manage' => 'Manage', 'fullscreen' => 'מסך מלא', 'favourite' => 'מועדף', 'unfavourite' => 'בטל מועדף', diff --git a/lang/he/entities.php b/lang/he/entities.php index 63e5fe3a3..21c9d7742 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'הכנס סרטוט', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'דף אינו חלק מפרק', 'pages_move' => 'העבר דף', 'pages_copy' => 'העתק דף', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/he/errors.php b/lang/he/errors.php index 6c133d860..181ef3db6 100644 --- a/lang/he/errors.php +++ b/lang/he/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/he/notifications.php b/lang/he/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/he/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/he/preferences.php b/lang/he/preferences.php index 95f86d761..bed6904cc 100644 --- a/lang/he/preferences.php +++ b/lang/he/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'קיצורי דרך', 'shortcuts_interface' => 'קיצורי מקשים של המערכת', 'shortcuts_toggle_desc' => 'כאן תוכל להפעיל או לבטל קיצורי דרך לממשק מערכת המקלדת, המשמשים לניווט ולפעולות.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'שמור קיצורי דרך', 'shortcuts_overlay_desc' => 'הערה: כאשר קיצורי דרך מופעלים, שכבת-על מסייעת זמינה באמצעות לחיצה על "?" אשר ידגיש את קיצורי הדרך הזמינים לפעולות הנראות כעת על המסך.', 'shortcuts_update_success' => 'העדפותיך נשמרו!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/he/settings.php b/lang/he/settings.php index 2fe468ac5..3423dc57a 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'ניהול הגדרות יישום', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'הרשאות משאבים', 'roles_system_warning' => 'שימו לב לכך שגישה לכל אחת משלושת ההרשאות הנ"ל יכולה לאפשר למשתמש לשנות את הפריווילגיות שלהם או של אחרים במערכת. הגדירו תפקידים להרשאות אלה למשתמשים בהם אתם בוטחים בלבד.', 'role_asset_desc' => 'הרשאות אלו שולטות בגישת ברירת המחדל למשאבים בתוך המערכת. הרשאות של ספרים, פרקים ודפים יגברו על הרשאות אלו.', diff --git a/lang/hr/activities.php b/lang/hr/activities.php index 25d333892..541f67646 100644 --- a/lang/hr/activities.php +++ b/lang/hr/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '".ime" će biti dodano u tvoje favorite', 'favourite_remove_notification' => '".ime" je uspješno maknuta iz tvojih favorita', + // Watching + 'watch_update_level_notification' => 'Postavke gledanja uspješno ažurirane', + // Auth 'auth_login' => 'prijavljen', 'auth_register' => 'registriran kao novi korisnik', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'reciklirano iz koša za smeće', 'recycle_bin_destroy' => 'uklonjeno iz koša za smeće', - // Other + // Comments 'commented_on' => 'komentirano', + 'comment_create' => 'dodani komentar', + 'comment_update' => 'ažurirani komentar', + 'comment_delete' => 'obrisani komentar', + + // Other 'permissions_update' => 'ažurirana dopuštenja', ]; diff --git a/lang/hr/common.php b/lang/hr/common.php index 9321a69fd..4de4cef2d 100644 --- a/lang/hr/common.php +++ b/lang/hr/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Ukloni', 'add' => 'Dodaj', 'configure' => 'Konfiguriraj', + 'manage' => 'Upravljaj', 'fullscreen' => 'Cijeli zaslon', 'favourite' => 'Favorit', 'unfavourite' => 'Ukloni iz favorita', diff --git a/lang/hr/entities.php b/lang/hr/entities.php index ab637ec0e..5f48eb1f1 100644 --- a/lang/hr/entities.php +++ b/lang/hr/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Ažurirana dopuštenja za Policu', 'shelves_permissions_active' => 'Aktivna Dopuštenja za Policu', 'shelves_permissions_cascade_warning' => 'Dozvole na policama se automatski ne prenose na knjige koje se nalaze na njima. To je zato što se jedna knjiga može nalaziti na više polica. Međutim, dozvole se mogu kopirati na podređene knjige koristeći opciju koja se nalazi ispod.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Dozvole za stvaranje police koriste se samo za kopiranje dozvola na podređene knjige pomoću radnje u nastavku. Ne kontroliraju sposobnost stvaranja knjiga.', 'shelves_copy_permissions_to_books' => 'Kopiraj dopuštenja za knjige', 'shelves_copy_permissions' => 'Kopiraj dopuštenja', 'shelves_copy_permissions_explain' => 'Ovo će primijeniti trenutne postavke dozvola ove police na sve knjige koje se nalaze na njoj. Prije aktiviranja, provjerite jeste li spremili sve promjene dozvola na ovoj polici.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Umetni crtež', 'pages_md_show_preview' => 'Prikaži pregled', 'pages_md_sync_scroll' => 'Sinkroniziraj pomicanje pregleda', + 'pages_drawing_unsaved' => 'Pronađen je Nespremljen Crtež', + 'pages_drawing_unsaved_confirm' => 'Pronađeni su nespremljeni podaci crteža iz prethodnog neuspjelog pokušaja spremanja crteža. Želite li obnoviti i nastaviti uređivati ovaj nespremljeni crtež?', 'pages_not_in_chapter' => 'Stranica nije u poglavlju', 'pages_move' => 'Premjesti stranicu', 'pages_copy' => 'Kopiraj stranicu', @@ -403,4 +405,28 @@ return [ 'references' => 'Reference', 'references_none' => 'Nema praćenih referenci na ovu stavku.', 'references_to_desc' => 'U nastavku su prikazane sve poznate stranice u sustavu koje se povezuju s ovom stavkom.', + + // Watch Options + 'watch' => 'Prati', + 'watch_title_default' => 'Zadane Postavke', + 'watch_desc_default' => 'Vratite praćenje samo na vaše zadane postavke obavijesti.', + 'watch_title_ignore' => 'Zanemari', + 'watch_desc_ignore' => 'Ignorirajte sve obavijesti, uključujući one iz postavki na razini korisnika.', + 'watch_title_new' => 'Nove Stranice', + 'watch_desc_new' => 'Obavijesti kada se stvori nova stranica unutar ove stavke.', + 'watch_title_updates' => 'Sve Promjene na Stranicama', + 'watch_desc_updates' => 'Obavijesti o svim novim stranicama i promjenama na stranicama.', + 'watch_desc_updates_page' => 'Obavijesti o svim promjenama na stranicama.', + 'watch_title_comments' => 'Sve Promjene na Stranicama i Komentari', + 'watch_desc_comments' => 'Obavijesti o svim novim stranicama, promjenama na stranicama i novim komentarima.', + 'watch_desc_comments_page' => 'Obavijesti o promjenama na stranicama i novim komentarima.', + 'watch_change_default' => 'Promijenite zadane postavke obavijesti', + 'watch_detail_ignore' => 'Ignoriranje obavijesti', + 'watch_detail_new' => 'Prati nove stranice', + 'watch_detail_updates' => 'Prati nove stranice i ažuriranja', + 'watch_detail_comments' => 'Prati nove stranice, ažuriranja i komentare', + 'watch_detail_parent_book' => 'Prati putem nadređene knjige', + 'watch_detail_parent_book_ignore' => 'Ignoriraj putem nadređene knjige', + 'watch_detail_parent_chapter' => 'Prati puten nadređenog poglavlja', + 'watch_detail_parent_chapter_ignore' => 'Ignoriraj putem nadređenog poglavlja', ]; diff --git a/lang/hr/errors.php b/lang/hr/errors.php index 401f8989d..8b97d8b2a 100644 --- a/lang/hr/errors.php +++ b/lang/hr/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Pogreška prilikom slanja testnog email:', + // HTTP errors + 'http_ssr_url_no_match' => 'URL se ne podudara s konfiguriranim dozvoljenim SSR domaćinima', ]; diff --git a/lang/hr/notifications.php b/lang/hr/notifications.php new file mode 100644 index 000000000..e50d57dfe --- /dev/null +++ b/lang/hr/notifications.php @@ -0,0 +1,28 @@ + 'Novi komentar na stranici: :pageName', + 'new_comment_intro' => 'Korisnik je komentirao stranicu u :appName:', + 'new_page_subject' => 'Nova stranica: :pageName', + 'new_page_intro' => 'Nova stranica je stvorena u :appName:', + 'updated_page_subject' => 'ChatGPT + +Ažurirana stranica: :pageName', + 'updated_page_intro' => 'Stranica je ažurirana u :appName:', + 'updated_page_debounce' => 'Kako biste spriječili velik broj obavijesti, nećete primati obavijesti o daljnjim izmjenama ove stranice od istog urednika neko vrijeme.', + + 'detail_page_name' => 'Naziv Stranice:', + 'detail_commenter' => 'Komentator:', + 'detail_comment' => 'Komentar:', + 'detail_created_by' => 'Kreirao Korisnik:', + 'detail_updated_by' => 'Ažurirao Korisnik:', + + 'action_view_comment' => 'Pogledaj Komentar', + 'action_view_page' => 'Pogledaj Stranicu', + + 'footer_reason' => 'Ova obavijest vam je poslana jer :link pokriva ovu vrstu aktivnosti za ovu stavku.', + 'footer_reason_link' => 'vaše postavke obavijesti', +]; diff --git a/lang/hr/preferences.php b/lang/hr/preferences.php index 706846aaf..a999067c8 100644 --- a/lang/hr/preferences.php +++ b/lang/hr/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Postavke', + 'shortcuts' => 'Prečaci', 'shortcuts_interface' => 'Prečaci tipkovnice u Sučelju', 'shortcuts_toggle_desc' => 'Ovdje možete omogućiti ili onemogućiti prečace tastature u korisničkom sučelju sustava koji se koriste za navigaciju i akcije.', @@ -15,4 +17,19 @@ return [ 'shortcuts_save' => 'Spremi prečace', 'shortcuts_overlay_desc' => 'Napomena: Kada su prečaci tastature omogućeni, dostupan je pomoćni prikaz preko pritiska na znak "?" koji će istaknuti dostupne prečace za radnje trenutno vidljive na zaslonu.', 'shortcuts_update_success' => 'Postavke prečaca su ažurirane!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Upravljajte prečacima tastature koje možete koristiti za navigaciju korisničkim sučeljem sustava.', + + 'notifications' => 'Postavke Obavijesti', + 'notifications_desc' => 'Kontrolirajte e-mail obavijesti koje primate kada se određene aktivnosti izvrše unutar sustava.', + 'notifications_opt_own_page_changes' => 'Obavijesti o promjenama na stranicama koje posjedujem', + 'notifications_opt_own_page_comments' => 'ChatGPT + +Obavijesti o komentarima na stranicama koje posjedujem', + 'notifications_opt_comment_replies' => 'Obavijesti o odgovorima na moje komentare', + 'notifications_save' => 'Spremi Postavke', + 'notifications_update_success' => 'Postavke obavijesti su ažurirane!', + 'notifications_watched' => 'Praćene i ignorirane stavke', + 'notifications_watched_desc' => ' Ispod su stavke na koje su primijenjene prilagođene postavke praćenja. Da biste ažurirali svoje postavke za ove stavke, pregledajte stavku, a zatim pronađite opcije praćenja u bočnoj traci.', + + 'profile_overview_desc' => ' Upravljajte detaljima svog korisničkog profila, uključujući željeni jezik i opcije za autentifikaciju.', +]; diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 1777dd50b..b3120b08a 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Upravljanje postavkama aplikacija', 'role_export_content' => 'Izvoz sadržaja', 'role_editor_change' => 'Promijeni uređivač stranica', + 'role_notifications' => 'Primanje i upravljanje obavijestima', 'role_asset' => 'Upravljanje vlasništvom', 'roles_system_warning' => 'Uzmite u obzir da pristup bilo kojem od ovih dopuštenja dozvoljavate korisniku upravljanje dopuštenjima ostalih u sustavu. Ova dopuštenja dodijelite pouzdanim korisnicima.', 'role_asset_desc' => 'Ova dopuštenja kontroliraju zadane pristupe. Dopuštenja za knjige, poglavlja i stranice ih poništavaju.', diff --git a/lang/hu/activities.php b/lang/hu/activities.php index 6d63a4902..f7e068d69 100644 --- a/lang/hu/activities.php +++ b/lang/hu/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'megjegyzést fűzött hozzá:', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'updated permissions', ]; diff --git a/lang/hu/common.php b/lang/hu/common.php index 2967cacee..48a47f5e3 100644 --- a/lang/hu/common.php +++ b/lang/hu/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Eltávolítás', 'add' => 'Hozzáadás', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Teljes képernyő', 'favourite' => 'Kedvencekhez ad', 'unfavourite' => 'Kedvencekből eltávolít', diff --git a/lang/hu/editor.php b/lang/hu/editor.php index 4662cf455..7c0da37bf 100644 --- a/lang/hu/editor.php +++ b/lang/hu/editor.php @@ -9,7 +9,7 @@ return [ // General editor terms 'general' => 'Általános', 'advanced' => 'Haladó', - 'none' => 'None', + 'none' => 'Egyik sem', 'cancel' => 'Mégsem', 'save' => 'Mentés', 'close' => 'Bezárás', @@ -24,7 +24,7 @@ return [ 'width' => 'Szélesség', 'height' => 'Magasság', 'More' => 'Több', - 'select' => 'Select...', + 'select' => 'Kiválasztás...', // Toolbar 'formats' => 'Formátumok', @@ -66,7 +66,7 @@ return [ 'insert_link_title' => 'Hivatkozás Beszúrása/Szerkesztése', 'insert_horizontal_line' => 'Vízszintes vonal beszúrása', 'insert_code_block' => 'Kódrészlet beszúrása', - 'edit_code_block' => 'Edit code block', + 'edit_code_block' => 'Kódrészlet beszúrása', 'insert_drawing' => 'Rajz beszúrása/szerkesztése', 'drawing_manager' => 'Rajzkezelő', 'insert_media' => 'Media beszúrása/szerkesztése', diff --git a/lang/hu/entities.php b/lang/hu/entities.php index 35613cff6..5fc079ecf 100644 --- a/lang/hu/entities.php +++ b/lang/hu/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Rajz beillesztése', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Az oldal nincs fejezetben', 'pages_move' => 'Oldal áthelyezése', 'pages_copy' => 'Oldal másolása', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/hu/errors.php b/lang/hu/errors.php index 87f4b32e7..5a0b6a0ed 100644 --- a/lang/hu/errors.php +++ b/lang/hu/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Hiba történt egy teszt email küldésekor:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/hu/notifications.php b/lang/hu/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/hu/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/hu/preferences.php b/lang/hu/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/hu/preferences.php +++ b/lang/hu/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/hu/settings.php b/lang/hu/settings.php index d81f30b34..51487542b 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Alkalmazás beállításainak kezelése', 'role_export_content' => 'Tartalom exportálása', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Eszköz jogosultságok', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'Ezek a jogosultságok vezérlik az alapértelmezés szerinti hozzáférést a rendszerben található eszközökhöz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.', diff --git a/lang/id/activities.php b/lang/id/activities.php index 22169db55..3337fcdd8 100644 --- a/lang/id/activities.php +++ b/lang/id/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" telah ditambahkan ke favorit Anda', 'favourite_remove_notification' => '":name" telah dihapus dari favorit Anda', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'berkomentar pada', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'izin diperbarui', ]; diff --git a/lang/id/common.php b/lang/id/common.php index 2f749972b..66f6ec1b4 100644 --- a/lang/id/common.php +++ b/lang/id/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Hapus', 'add' => 'Tambah', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Layar Penuh', 'favourite' => 'Favorit', 'unfavourite' => 'Batal favorit', diff --git a/lang/id/entities.php b/lang/id/entities.php index ce39b0723..b78887a82 100644 --- a/lang/id/entities.php +++ b/lang/id/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Sisipkan Gambar', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Halaman tidak dalam satu bab', 'pages_move' => 'Pindahkan Halaman', 'pages_copy' => 'Salin Halaman', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/id/errors.php b/lang/id/errors.php index 83d300888..70a077270 100644 --- a/lang/id/errors.php +++ b/lang/id/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Kesalahan dilempar saat mengirim email uji:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/id/notifications.php b/lang/id/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/id/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/id/preferences.php b/lang/id/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/id/preferences.php +++ b/lang/id/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/id/settings.php b/lang/id/settings.php index 85d3ceef0..ea5f55f38 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Kelola setelan aplikasi', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Izin Aset', 'roles_system_warning' => 'Ketahuilah bahwa akses ke salah satu dari tiga izin di atas dapat memungkinkan pengguna untuk mengubah hak mereka sendiri atau orang lain dalam sistem. Hanya tetapkan peran dengan izin ini untuk pengguna tepercaya.', 'role_asset_desc' => 'Izin ini mengontrol akses default ke aset dalam sistem. Izin pada Buku, Bab, dan Halaman akan menggantikan izin ini.', diff --git a/lang/it/activities.php b/lang/it/activities.php index f5fc6d8a5..ba07709c0 100644 --- a/lang/it/activities.php +++ b/lang/it/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" è stato aggiunto ai tuoi preferiti', 'favourite_remove_notification' => '":name" è stato rimosso dai tuoi preferiti', + // Watching + 'watch_update_level_notification' => 'Preferenze di monitoraggio aggiornate con successo', + // Auth 'auth_login' => 'connesso', 'auth_register' => 'registrato come nuovo utente', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'ripristinato dal cestino', 'recycle_bin_destroy' => 'rimosso dal cestino', - // Other + // Comments 'commented_on' => 'ha commentato in', + 'comment_create' => 'commento aggiunto', + 'comment_update' => 'commento aggiornato', + 'comment_delete' => 'commento rimosso', + + // Other 'permissions_update' => 'autorizzazioni aggiornate', ]; diff --git a/lang/it/common.php b/lang/it/common.php index 6a7af0b1e..14a3e271d 100644 --- a/lang/it/common.php +++ b/lang/it/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Rimuovi', 'add' => 'Aggiungi', 'configure' => 'Configura', + 'manage' => 'Gestisci', 'fullscreen' => 'Schermo intero', 'favourite' => 'Aggiungi ai Preferiti', 'unfavourite' => 'Rimuovi dai preferiti', diff --git a/lang/it/entities.php b/lang/it/entities.php index e8941f630..0b635c503 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permessi Libreria Aggiornati', 'shelves_permissions_active' => 'Permessi Libreria Attivi', 'shelves_permissions_cascade_warning' => 'I permessi delle librerie non si estendono automaticamente ai libri contenuti. Questo perché un libro può essere presente su più scaffali. I permessi possono comunque essere copiati ai libri al suo interno usando l\'opzione sottostante.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Le autorizzazioni per la creazione di librerie sono utilizzate solo per copiare le autorizzazioni ai libri figli utilizzando l\'azione sottostante. Non controllano la capacità di creare libri.', 'shelves_copy_permissions_to_books' => 'Copia Permessi ai Libri', 'shelves_copy_permissions' => 'Copia Permessi', 'shelves_copy_permissions_explain' => 'Verranno applicati tutti i permessi della libreria ai libri al suo interno. Prima dell\'attivazione, assicurati che ogni permesso di questa libreria sia salvato.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Inserisci Disegno', 'pages_md_show_preview' => 'Visualizza anteprima', 'pages_md_sync_scroll' => 'Sincronizza scorrimento anteprima', + 'pages_drawing_unsaved' => 'Trovato Disegno Non Salvato', + 'pages_drawing_unsaved_confirm' => 'Sono stati trovati i dati di un disegno non salvati da un precedente tentativo di salvataggio di disegno non riuscito. Ripristinare e continuare a modificare questo disegno non salvato?', 'pages_not_in_chapter' => 'La pagina non è in un capitolo', 'pages_move' => 'Muovi Pagina', 'pages_copy' => 'Copia Pagina', @@ -403,4 +405,28 @@ return [ 'references' => 'Riferimenti', 'references_none' => 'Non ci sono riferimenti tracciati a questa voce.', 'references_to_desc' => 'Di seguito sono riportate tutte le pagine conosciute nel sistema che collegano a questo elemento.', + + // Watch Options + 'watch' => 'Osserva', + 'watch_title_default' => 'Preferenze Predefinite', + 'watch_desc_default' => 'Ripristina la visualizzazione delle tue preferenze di notifica predefinite.', + 'watch_title_ignore' => 'Ignora', + 'watch_desc_ignore' => 'Ignora tutte le notifiche, comprese quelle dalle preferenze di livello utente.', + 'watch_title_new' => 'Nuove Pagine', + 'watch_desc_new' => 'Notifica quando viene creata una nuova pagina all\'interno di questo elemento.', + 'watch_title_updates' => 'Tutti Gli Aggiornamenti Della Pagina', + 'watch_desc_updates' => 'Notificare su tutte le nuove pagine e modifiche di pagina.', + 'watch_desc_updates_page' => 'Notifica su tutte le modifiche alla pagina.', + 'watch_title_comments' => 'Tutti Gli Aggiornamenti Della Pagina E Commenti', + 'watch_desc_comments' => 'Notificare su tutte le nuove pagine, modifiche di pagina e nuovi commenti.', + 'watch_desc_comments_page' => 'Notificare le modifiche alla pagina e i nuovi commenti.', + 'watch_change_default' => 'Modifica le preferenze di notifica predefinite', + 'watch_detail_ignore' => 'Ignorare le notifiche', + 'watch_detail_new' => 'In attesa di nuove pagine', + 'watch_detail_updates' => 'Osservare le nuove pagine e gli aggiornamenti', + 'watch_detail_comments' => 'Osservare le nuove pagine, aggiornamenti e commenti', + 'watch_detail_parent_book' => 'Osservare tramite il libro madre', + 'watch_detail_parent_book_ignore' => 'Ignorato tramite il libro madre', + 'watch_detail_parent_chapter' => 'Osservare tramite il capitolo madre', + 'watch_detail_parent_chapter_ignore' => 'Ignorato tramite il capitolo madre', ]; diff --git a/lang/it/errors.php b/lang/it/errors.php index 5418beb14..3a37031c9 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Si è verificato un errore durante l\'invio di una e-mail di prova:', + // HTTP errors + 'http_ssr_url_no_match' => 'L\'URL non corrisponde agli host SSR configurati', ]; diff --git a/lang/it/notifications.php b/lang/it/notifications.php new file mode 100644 index 000000000..a17abe368 --- /dev/null +++ b/lang/it/notifications.php @@ -0,0 +1,26 @@ + 'Nuovo commento alla pagina: :pageName', + 'new_comment_intro' => 'Un utente ha commentato una pagina in :appName:', + 'new_page_subject' => 'Nuova pagina: :pageName', + 'new_page_intro' => 'Una nuova pagina è stata creata in :appName:', + 'updated_page_subject' => 'Pagina aggiornata: :pageName', + 'updated_page_intro' => 'Una pagina è stata aggiornata in :appName:', + 'updated_page_debounce' => 'Per evitare una massa di notifiche, per un po \'non verranno inviate notifiche per ulteriori modifiche a questa pagina dallo stesso editor.', + + 'detail_page_name' => 'Nome Della Pagina:', + 'detail_commenter' => 'Commentatore:', + 'detail_comment' => 'Commento:', + 'detail_created_by' => 'Creato Da:', + 'detail_updated_by' => 'Aggiornato Da:', + + 'action_view_comment' => 'Visualizza Commento', + 'action_view_page' => 'Visualizza Pagina', + + 'footer_reason' => 'Questa notifica è stata inviata perché :link copre questo tipo di attività per questo elemento.', + 'footer_reason_link' => 'le tue preferenze di notifica', +]; diff --git a/lang/it/preferences.php b/lang/it/preferences.php index 06cc17280..ac70d095e 100644 --- a/lang/it/preferences.php +++ b/lang/it/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferenze', + 'shortcuts' => 'Scorciatoie', 'shortcuts_interface' => 'Interfaccia scorciatoie da tastiera', 'shortcuts_toggle_desc' => 'Qui puoi abilitare o disabilitare le scorciatoie dell\'interfaccia di sistema da tastiera, utilizzate per la navigazione e le azioni.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Salva Scorciatoie', 'shortcuts_overlay_desc' => 'Nota: quando le scorciatoie sono abilitate, premendo "?" è possibile visualizzare le scorciatoie disponibili per le azioni attualmente visibili sullo schermo.', 'shortcuts_update_success' => 'Le preferenze delle scorciatoie sono state aggiornate!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gestisci le scorciatoie da tastiera che puoi usare per navigare nell\'interfaccia utente di sistema.', + + 'notifications' => 'Preferenze Di Notifica', + 'notifications_desc' => 'Controlla le notifiche email che ricevi quando viene eseguita una determinata attività all\'interno del sistema.', + 'notifications_opt_own_page_changes' => 'Notifica in caso di modifiche alle pagine che possiedo', + 'notifications_opt_own_page_comments' => 'Notifica i commenti sulle pagine che possiedo', + 'notifications_opt_comment_replies' => 'Notificare le risposte ai miei commenti', + 'notifications_save' => 'Salva Preferenze', + 'notifications_update_success' => 'Le preferenze di notifica sono state aggiornate!', + 'notifications_watched' => 'Oggetti Osservati E Ignorati', + 'notifications_watched_desc' => 'Di seguito sono riportati gli articoli a cui sono state applicate le preferenze di monitoraggio personalizzate. Per aggiornare le preferenze, visualizzare l\'articolo e trovare le opzioni di monitoraggio nella barra laterale.', + + 'profile_overview_desc' => ' Gestisci i dettagli del tuo profilo utente, incluse la lingua preferita e le opzioni di autenticazione.', +]; diff --git a/lang/it/settings.php b/lang/it/settings.php index 47599bce2..369b6677a 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gestire impostazioni app', 'role_export_content' => 'Esporta contenuto', 'role_editor_change' => 'Cambia editor di pagina', + 'role_notifications' => 'Ricevi & gestisci le notifiche', '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.', diff --git a/lang/ja/activities.php b/lang/ja/activities.php index dc226fa68..0e6e8d3ce 100644 --- a/lang/ja/activities.php +++ b/lang/ja/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name"がお気に入りに追加されました', 'favourite_remove_notification' => '":name"がお気に入りから削除されました', + // Watching + 'watch_update_level_notification' => 'ウォッチ設定を更新しました', + // Auth 'auth_login' => 'がログイン', 'auth_register' => 'が新規ユーザ登録', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'がゴミ箱から復元', 'recycle_bin_destroy' => 'がゴミ箱から完全に削除', - // Other + // Comments 'commented_on' => 'がコメント:', + 'comment_create' => 'がコメントを追加', + 'comment_update' => 'がコメントを更新', + 'comment_delete' => 'がコメントを削除', + + // Other 'permissions_update' => 'が権限を更新:', ]; diff --git a/lang/ja/common.php b/lang/ja/common.php index bff79ea81..7ea68c798 100644 --- a/lang/ja/common.php +++ b/lang/ja/common.php @@ -42,6 +42,7 @@ return [ 'remove' => '削除', 'add' => '追加', 'configure' => '設定', + 'manage' => '管理', 'fullscreen' => '全画面', 'favourite' => 'お気に入り', 'unfavourite' => 'お気に入りから削除', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index 89da95e2d..d82895512 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => '本棚の権限を更新しました', 'shelves_permissions_active' => '本棚の権限は有効です', 'shelves_permissions_cascade_warning' => '本棚の権限は含まれる本には自動的に継承されません。これは、1つのブックが複数の本棚に存在する可能性があるためです。ただし、以下のオプションを使用すると権限を子ブックにコピーできます。', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => '本棚の作成権限は、以下のアクションを使用した子ブックへの権限コピーにのみ使用されます。これはブックの作成を制御するものではありません。', 'shelves_copy_permissions_to_books' => 'ブックに権限をコピー', 'shelves_copy_permissions' => '権限をコピー', 'shelves_copy_permissions_explain' => 'これにより、この本棚の現在の権限設定を本棚に含まれるすべてのブックに適用します。有効にする前に、この本棚の権限への変更が保存されていることを確認してください。', @@ -236,9 +236,12 @@ return [ 'pages_md_preview' => 'プレビュー', 'pages_md_insert_image' => '画像を挿入', 'pages_md_insert_link' => 'エンティティへのリンクを挿入', - 'pages_md_insert_drawing' => '描画を追加', + 'pages_md_insert_drawing' => '図を追加', 'pages_md_show_preview' => 'プレビューを表示', 'pages_md_sync_scroll' => 'プレビューとスクロールを同期', + 'pages_drawing_unsaved' => '未保存の図が見つかりました', + 'pages_drawing_unsaved_confirm' => '以前に保存操作が失敗した、未保存の図が見つかりました。 +未保存の図面を復元して編集を続けますか?', 'pages_not_in_chapter' => 'チャプターが設定されていません', 'pages_move' => 'ページを移動', 'pages_copy' => 'ページをコピー', @@ -361,7 +364,7 @@ return [ 'comments' => 'コメント', 'comment_add' => 'コメント追加', 'comment_placeholder' => 'コメントを記入してください', - 'comment_count' => '{0} コメントはありません|[1,*] コメント:count件', + 'comment_count' => '{0} コメントはありません|[1,*] :count 件のコメント', 'comment_save' => 'コメントを保存', 'comment_new' => '新規コメント作成', 'comment_created' => 'コメントを作成しました :createDiff', @@ -403,4 +406,28 @@ return [ 'references' => '参照', 'references_none' => 'この項目への追跡された参照はありません。', 'references_to_desc' => 'この項目にリンクしている、システム内のすべての既知のページを以下に示します。', + + // Watch Options + 'watch' => 'ウォッチ', + 'watch_title_default' => 'デフォルト設定', + 'watch_desc_default' => 'デフォルトの通知設定に戻します。', + 'watch_title_ignore' => '無効', + 'watch_desc_ignore' => 'ユーザーの通知設定に関わらず、すべての通知を無効にします。', + 'watch_title_new' => 'ページの作成', + 'watch_desc_new' => 'このアイテム内に新しいページが作成されたときに通知します。', + 'watch_title_updates' => 'すべてのページ更新', + 'watch_desc_updates' => 'ページの作成や更新を通知します。', + 'watch_desc_updates_page' => 'ページの更新を通知します。', + 'watch_title_comments' => 'すべてのページ更新とコメント', + 'watch_desc_comments' => 'ページの作成・更新、およびコメント追加を通知します。', + 'watch_desc_comments_page' => 'ページの更新およびコメント追加を通知します。', + 'watch_change_default' => 'デフォルトの通知設定を変更する', + 'watch_detail_ignore' => '通知無効', + 'watch_detail_new' => 'ページ作成をウォッチ', + 'watch_detail_updates' => 'ページの作成と更新をウォッチ', + 'watch_detail_comments' => 'ページの作成・更新とコメントをウォッチ', + 'watch_detail_parent_book' => '親ブックでウォッチ', + 'watch_detail_parent_book_ignore' => '親ブックで通知無効', + 'watch_detail_parent_chapter' => '親チャプタでウォッチ', + 'watch_detail_parent_chapter_ignore' => '親チャプタで通知無効', ]; diff --git a/lang/ja/errors.php b/lang/ja/errors.php index 3e454c30e..1ed1fe235 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'テストメール送信時にエラーが発生しました:', + // HTTP errors + 'http_ssr_url_no_match' => 'URLはサーバサイドリクエストが許可されたホストではありません。', ]; diff --git a/lang/ja/notifications.php b/lang/ja/notifications.php new file mode 100644 index 000000000..820b13c8f --- /dev/null +++ b/lang/ja/notifications.php @@ -0,0 +1,26 @@ + 'ページへのコメント追加: :pageName', + 'new_comment_intro' => ':appName でページにコメントが追加されました', + 'new_page_subject' => 'ページの作成: :pageName', + 'new_page_intro' => ':appName でページが作成されました', + 'updated_page_subject' => 'ページの更新: :pageName', + 'updated_page_intro' => ':appName でページが更新されました', + 'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。', + + 'detail_page_name' => 'ページ名:', + 'detail_commenter' => 'コメントユーザ:', + 'detail_comment' => 'コメント:', + 'detail_created_by' => '作成ユーザ:', + 'detail_updated_by' => '更新ユーザ:', + + 'action_view_comment' => 'コメントを表示', + 'action_view_page' => 'ページを表示', + + 'footer_reason' => 'この項目のアクティビティは :link による対象となっているため、この通知が送信されました。', + 'footer_reason_link' => '通知設定', +]; diff --git a/lang/ja/preferences.php b/lang/ja/preferences.php index 36a2f5a99..7f650059e 100644 --- a/lang/ja/preferences.php +++ b/lang/ja/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => '設定', + 'shortcuts' => 'ショートカット', 'shortcuts_interface' => 'インターフェイスのキーボードショートカット', 'shortcuts_toggle_desc' => 'ここでは、ナビゲーションやアクションに使用されるキーボードシステムインターフェイスのショートカットを有効または無効にすることができます。', @@ -14,5 +16,18 @@ return [ 'shortcuts_section_actions' => '共通のアクション', 'shortcuts_save' => 'ショートカットを保存', 'shortcuts_overlay_desc' => '注:ショートカットが有効な場合はヘルパーオーバーレイが利用できます。「?」を押すと現在画面に表示されているアクションで利用可能なショートカットをハイライト表示します。', - 'shortcuts_update_success' => 'ショートカットの設定が更新されました!', -]; \ No newline at end of file + 'shortcuts_update_success' => 'ショートカットの設定を更新しました。', + 'shortcuts_overview_desc' => 'システムのユーザーインターフェイスを操作するためのキーボードショートカットを管理します。', + + 'notifications' => '通知設定', + 'notifications_desc' => 'システム内で特定のアクティビティが実行されたときに受信する電子メール通知を制御します。', + 'notifications_opt_own_page_changes' => '自分が所有するページの変更を通知する', + 'notifications_opt_own_page_comments' => '自分が所有するページへのコメントを通知する', + 'notifications_opt_comment_replies' => '自分のコメントへの返信を通知する', + 'notifications_save' => '設定を保存', + 'notifications_update_success' => '通知設定を更新しました。', + 'notifications_watched' => 'ウォッチ/通知無効 項目', + 'notifications_watched_desc' => ' 以下はカスタムウォッチの設定が適用されている項目です。 これらの設定を更新するには、項目を表示してサイドバーのウォッチオプションを参照してください。', + + 'profile_overview_desc' => ' 言語や認証オプションを含むユーザープロファイルの詳細を管理します。', +]; diff --git a/lang/ja/settings.php b/lang/ja/settings.php index c0536771e..1f4eabb56 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'アプリケーション設定の管理', 'role_export_content' => 'コンテンツのエクスポート', 'role_editor_change' => 'ページエディタの変更', + 'role_notifications' => '通知の受信と管理', 'role_asset' => 'アセット権限', 'roles_system_warning' => '上記の3つの権限のいずれかを付与することは、ユーザーが自分の特権またはシステム内の他のユーザーの特権を変更できる可能性があることに注意してください。これらの権限は信頼できるユーザーにのみ割り当ててください。', 'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。', diff --git a/lang/ka/activities.php b/lang/ka/activities.php index e71a490de..d5b55c03d 100644 --- a/lang/ka/activities.php +++ b/lang/ka/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'commented on', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'updated permissions', ]; diff --git a/lang/ka/common.php b/lang/ka/common.php index de7937b2b..47b74d5b6 100644 --- a/lang/ka/common.php +++ b/lang/ka/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remove', 'add' => 'Add', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Fullscreen', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/ka/entities.php b/lang/ka/entities.php index 4fb043aa9..4468cd68f 100644 --- a/lang/ka/entities.php +++ b/lang/ka/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/ka/errors.php b/lang/ka/errors.php index 23c326f9e..4cde4cea3 100644 --- a/lang/ka/errors.php +++ b/lang/ka/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ka/notifications.php b/lang/ka/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/ka/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/ka/preferences.php b/lang/ka/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/ka/preferences.php +++ b/lang/ka/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/ka/settings.php b/lang/ka/settings.php index c110e8992..8821c77f0 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Manage app settings', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', diff --git a/lang/ko/activities.php b/lang/ko/activities.php index b5ebe79f6..96313de1a 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" 북마크에 추가함', 'favourite_remove_notification' => '":name" 북마크에서 삭제함', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => '댓글 쓰기', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => '권한 수정함', ]; diff --git a/lang/ko/common.php b/lang/ko/common.php index 2ffa65d1e..dec41631a 100644 --- a/lang/ko/common.php +++ b/lang/ko/common.php @@ -42,6 +42,7 @@ return [ 'remove' => '제거', 'add' => '추가', 'configure' => '설정', + 'manage' => 'Manage', 'fullscreen' => '전체화면', 'favourite' => '즐겨찾기', 'unfavourite' => '즐겨찾기 해제', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index 5b3476543..f7480e8d3 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => '드로잉 추가', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => '챕터에 있는 문서가 아닙니다.', 'pages_move' => '문서 이동하기', 'pages_copy' => '문서 복제', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/ko/errors.php b/lang/ko/errors.php index 03f0bb15d..6f9dc72ca 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => '메일을 발송하는 도중 문제가 생겼습니다:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ko/notifications.php b/lang/ko/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/ko/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/ko/preferences.php b/lang/ko/preferences.php index 400e0f5ef..c312db8c0 100644 --- a/lang/ko/preferences.php +++ b/lang/ko/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => '단축키', 'shortcuts_interface' => '키보드 단축키', 'shortcuts_toggle_desc' => '여기에서 탐색과 행동에 사용될 수 있는 키보드 단축키를 활성화하거나 비활성화할 수 있습니다.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => '단축키 저장', 'shortcuts_overlay_desc' => '참고: 바로가기가 활성화된 경우 "?"를 누르면 현재 화면에 표시되는 작업에 대해 사용 가능한 바로가기를 강조 표시하는 도우미 오버레이를 사용할 수 있습니다.', 'shortcuts_update_success' => '단축키 설정이 수정되었습니다!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/ko/settings.php b/lang/ko/settings.php index e6a08bb27..17bd83abf 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => '사이트 설정 관리', 'role_export_content' => '항목 내보내기', 'role_editor_change' => '페이지 편집기 변경', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => '권한 항목', 'roles_system_warning' => '위 세 권한은 자신의 권한이나 다른 유저의 권한을 바꿀 수 있습니다.', 'role_asset_desc' => '책, 챕터, 문서별 권한은 이 설정에 우선합니다.', diff --git a/lang/lt/activities.php b/lang/lt/activities.php index d06bbfe1d..42322ffbc 100644 --- a/lang/lt/activities.php +++ b/lang/lt/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'pakomentavo', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'atnaujinti leidimai', ]; diff --git a/lang/lt/common.php b/lang/lt/common.php index ebe6eced2..a0f9faabb 100644 --- a/lang/lt/common.php +++ b/lang/lt/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Pašalinti', 'add' => 'Pridėti', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Visas ekranas', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index 5d07b8474..633f2b653 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Įterpti piešinį', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Puslapio nėra skyriuje', 'pages_move' => 'Perkelti puslapį', 'pages_copy' => 'Nukopijuoti puslapį', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/lt/errors.php b/lang/lt/errors.php index 5bba03cc2..bf4350fff 100644 --- a/lang/lt/errors.php +++ b/lang/lt/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Siunčiant bandymo email: įvyko klaida', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/lt/notifications.php b/lang/lt/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/lt/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/lt/preferences.php b/lang/lt/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/lt/preferences.php +++ b/lang/lt/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 690e1c8d0..eeeb271e9 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Tvarkyti programos nustatymus', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Nuosavybės leidimai', 'roles_system_warning' => 'Būkite sąmoningi, kad prieiga prie bet kurio iš trijų leidimų viršuje gali leisti naudotojui pakeisti jų pačių privilegijas arba kitų privilegijas sistemoje. Paskirkite vaidmenis su šiais leidimais tik patikimiems naudotojams.', 'role_asset_desc' => 'Šie leidimai kontroliuoja numatytą prieigą į nuosavybę, esančią sistemoje. Knygų, skyrių ir puslapių leidimai nepaisys šių leidimų.', diff --git a/lang/lv/activities.php b/lang/lv/activities.php index dc6206d56..2e0c9e609 100644 --- a/lang/lv/activities.php +++ b/lang/lv/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" ir pievienots jūsu favorītiem', 'favourite_remove_notification' => '":name" ir izņemts no jūsu favorītiem', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'komentēts', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'atjaunoja atļaujas', ]; diff --git a/lang/lv/common.php b/lang/lv/common.php index 27ea89de0..52e9e37ce 100644 --- a/lang/lv/common.php +++ b/lang/lv/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Noņemt', 'add' => 'Pievienot', 'configure' => 'Mainīt konfigurāciju', + 'manage' => 'Manage', 'fullscreen' => 'Pilnekrāns', 'favourite' => 'Pievienot favorītiem', 'unfavourite' => 'Noņemt no favorītiem', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index 095f20ff5..2d7f476e2 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Ievietot zīmējumu', 'pages_md_show_preview' => 'Rādīt priekšskatu', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Lapa nav nodaļā', 'pages_move' => 'Pārvietot lapu', 'pages_copy' => 'Kopēt lapu', @@ -403,4 +405,28 @@ return [ 'references' => 'Atsauces', 'references_none' => 'Uz šo vienumu nav atrasta neviena atsauce.', 'references_to_desc' => 'Zemāk parādītas visas sistēmā atrastās lapas, kas norāda uz šo vienumu.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/lv/errors.php b/lang/lv/errors.php index bcab6f474..74a480314 100644 --- a/lang/lv/errors.php +++ b/lang/lv/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Radusies kļūda sūtot testa epastu:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/lv/notifications.php b/lang/lv/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/lv/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/lv/preferences.php b/lang/lv/preferences.php index 7a2f3efb1..a3692cf09 100644 --- a/lang/lv/preferences.php +++ b/lang/lv/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Saīsnes', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Saglabāt saīsnes', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Saīsņu uzstādījumi ir saglabāt!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 0f099e4c8..fc9c6af3f 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Pārvaldīt iestatījumus', 'role_export_content' => 'Eksportēt saturu', 'role_editor_change' => 'Mainīt lapu redaktoru', + 'role_notifications' => 'Receive & manage notifications', '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.', diff --git a/lang/nb/activities.php b/lang/nb/activities.php index 0e8f2e746..c49a09c26 100644 --- a/lang/nb/activities.php +++ b/lang/nb/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'gjenopprettet side', 'page_restore_notification' => 'Siden ble gjenopprettet', 'page_move' => 'flyttet side', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Siden ble flyttet', // Chapters 'chapter_create' => 'opprettet kapittel', @@ -26,7 +26,7 @@ return [ 'chapter_delete_notification' => 'Kapittelet ble slettet', 'chapter_move' => 'flyttet kapittel ', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Kapitelet ble flyttet', // Books 'book_create' => 'opprettet bok', @@ -51,28 +51,31 @@ return [ 'bookshelf_delete_notification' => 'Hyllen ble slettet', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'gjenopprettet revisjon', + 'revision_delete' => 'slettet revisjon', + 'revision_delete_notification' => 'Revisjon slettet', // Favourites 'favourite_add_notification' => '«:name» ble lagt til i dine favoritter', 'favourite_remove_notification' => '«:name» ble fjernet fra dine favoritter', + // Watching + 'watch_update_level_notification' => 'Overvåkingsinnstillingene ble oppdatert', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'logget inn', + 'auth_register' => 'registrert som ny bruker', + 'auth_password_reset_request' => 'etterspurt tilbakestilling av passord', + 'auth_password_reset_update' => 'tilbakestill bruker passord', + 'mfa_setup_method' => 'konfigurert MFA-metode', 'mfa_setup_method_notification' => 'Flerfaktor-metoden ble konfigurert', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'fjernet MFA-metode', 'mfa_remove_method_notification' => 'Flerfaktor-metoden ble fjernet', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'oppdaterte innstillinger', + 'settings_update_notification' => 'Innstillingene er oppdatert', + 'maintenance_action_run' => 'kjørte vedlikeholdshandling', // Webhooks 'webhook_create' => 'opprettet webhook', @@ -83,35 +86,40 @@ return [ 'webhook_delete_notification' => 'Webhook ble slettet', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'opprettet bruker', + 'user_create_notification' => 'Bruker ble opprettet', + 'user_update' => 'oppdatert bruker', 'user_update_notification' => 'Brukeren ble oppdatert', - 'user_delete' => 'deleted user', + 'user_delete' => 'slettet bruker', 'user_delete_notification' => 'Brukeren ble fjernet', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'opprettet api token', + 'api_token_create_notification' => 'API-token er opprettet', + 'api_token_update' => 'oppdatert api token', + 'api_token_update_notification' => 'API-token oppdatert', + 'api_token_delete' => 'slettet api token', + 'api_token_delete_notification' => 'API-token ble slettet', // Roles - 'role_create' => 'created role', + 'role_create' => 'opprettet rolle', 'role_create_notification' => 'Rollen ble opprettet', - 'role_update' => 'updated role', + 'role_update' => 'oppdatert rolle', 'role_update_notification' => 'Rollen ble oppdatert', - 'role_delete' => 'deleted role', + 'role_delete' => 'slettet rolle', 'role_delete_notification' => 'Rollen ble fjernet', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'tømt resirkulering bin', + 'recycle_bin_restore' => 'gjenopprettet fra papirkurven', + 'recycle_bin_destroy' => 'fjernet fra papirkurven', + + // Comments + 'commented_on' => 'kommenterte på', + 'comment_create' => 'lagt til kommentar', + 'comment_update' => 'oppdatert kommentar', + 'comment_delete' => 'slettet kommentar', // Other - 'commented_on' => 'kommenterte på', 'permissions_update' => 'oppdaterte tilganger', ]; diff --git a/lang/nb/common.php b/lang/nb/common.php index 93495a354..b6b378efb 100644 --- a/lang/nb/common.php +++ b/lang/nb/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Fjern', 'add' => 'Legg til', 'configure' => 'Konfigurer', + 'manage' => 'Administrer', 'fullscreen' => 'Fullskjerm', 'favourite' => 'Favorisér', 'unfavourite' => 'Avfavorisér', diff --git a/lang/nb/entities.php b/lang/nb/entities.php index 4906411dc..74e39fff7 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Oppdaterte hyllerettigheter', 'shelves_permissions_active' => 'Aktiverte hyllerettigheter', 'shelves_permissions_cascade_warning' => 'Rettigheter på en hylle blir ikke automatisk arvet av bøker på hylla. Dette er fordi en bok kan finnes på flere hyller samtidig. Rettigheter kan likevel kopieres til bøker på hylla ved å bruke alternativene under.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Bokhylle-tillatelser brukes kun for kopiering av tillatelser til under-bøker ved hjelp av handlingen nedenfor. De kontrollerer ikke muligheten til å lage bøker.', 'shelves_copy_permissions_to_books' => 'Kopier tilganger til bøkene på hylla', 'shelves_copy_permissions' => 'Kopier tilganger', 'shelves_copy_permissions_explain' => 'Dette vil kopiere rettighetene på denne hylla til alle bøkene som er plassert på den. Før du starter kopieringen bør du sjekke at rettighetene på hylla er lagret først.', @@ -214,7 +214,7 @@ return [ 'pages_editing_page' => 'Redigerer side', 'pages_edit_draft_save_at' => 'Sist lagret ', 'pages_edit_delete_draft' => 'Slett utkast', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => 'Er du sikker på at du vil slette utkastendringer i utkastet? Alle dine endringer, siden siste lagring vil gå tapt, og editoren vil bli oppdatert med den siste siden uten utkast til lagring.', 'pages_edit_discard_draft' => 'Tilbakestill endring', 'pages_edit_switch_to_markdown' => 'Bytt til Markdown tekstredigering', 'pages_edit_switch_to_markdown_clean' => '(Renset innhold)', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Sett inn tegning', 'pages_md_show_preview' => 'Forhåndsvisning', 'pages_md_sync_scroll' => 'Synkroniser forhåndsvisningsrulle', + 'pages_drawing_unsaved' => 'Ulagret tegning funnet', + 'pages_drawing_unsaved_confirm' => 'Ulagret tegningsdata ble funnet fra en tidligere mislykket lagring. Vil du gjenopprette og fortsette å redigere denne ulagrede tegningen?', 'pages_not_in_chapter' => 'Siden tilhører ingen kapittel', 'pages_move' => 'Flytt side', 'pages_copy' => 'Kopiér side', @@ -287,8 +289,8 @@ return [ 'time_b' => 'i løpet av de siste :minCount minuttene', 'message' => ':start :time. Prøv å ikke overskriv hverandres endringer!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'Utkastet er forkastet! Redigeringsprogrammet er oppdatert med gjeldende sideinnhold', + 'pages_draft_deleted' => 'Utkast slettet! Redigeringsprogrammet er oppdatert med gjeldende sideinnhold', 'pages_specific' => 'Bestemt side', 'pages_is_template' => 'Sidemal', @@ -366,13 +368,13 @@ return [ 'comment_new' => 'Ny kommentar', 'comment_created' => 'kommenterte :createDiff', 'comment_updated' => 'Oppdatert :updateDiff av :username', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Oppdatert', 'comment_deleted_success' => 'Kommentar fjernet', 'comment_created_success' => 'Kommentar skrevet', 'comment_updated_success' => 'Kommentar endret', 'comment_delete_confirm' => 'Er du sikker på at du vil fjerne kommentaren?', 'comment_in_reply_to' => 'Som svar til :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'Her er kommentarene som er på denne siden. Kommentarer kan legges til og administreres når du ser på den lagrede siden.', // Revision 'revision_delete_confirm' => 'Vil du slette revisjonen?', @@ -403,4 +405,28 @@ return [ 'references' => 'Referanser', 'references_none' => 'Det er ingen sporede referanser til dette elementet.', 'references_to_desc' => 'Nedenfor vises alle de kjente sidene i systemet som lenker til denne oppføringen.', + + // Watch Options + 'watch' => 'Overvåk', + 'watch_title_default' => 'Standardinnstillinger', + 'watch_desc_default' => 'Bytt til dine standardinnstilleringer for varsling.', + 'watch_title_ignore' => 'Ignorer', + 'watch_desc_ignore' => 'Ignorer alle varslinger, inkludert de fra preferanser for brukernivå.', + 'watch_title_new' => 'Nye sider', + 'watch_desc_new' => 'Varsle når en ny side er opprettet innenfor dette elementet.', + 'watch_title_updates' => 'Alle sideoppdateringer', + 'watch_desc_updates' => 'Varsle på alle nye sider og endringer av siden.', + 'watch_desc_updates_page' => 'Varsle ved alle sideendringer.', + 'watch_title_comments' => 'Alle sideoppdateringer og kommentarer', + 'watch_desc_comments' => 'Varsle om alle nye sider, endringer på side og nye kommentarer.', + 'watch_desc_comments_page' => 'Varsle ved sideendringer og nye kommentarer.', + 'watch_change_default' => 'Endre standard varslingsinnstillinger', + 'watch_detail_ignore' => 'Ignorerer varsler', + 'watch_detail_new' => 'Varsling for nye sider', + 'watch_detail_updates' => 'Varsling for nye sider og oppdateringer', + 'watch_detail_comments' => 'Varsling for nye sider, oppdateringer og kommentarer', + 'watch_detail_parent_book' => 'Overvåker via overordnet bok', + 'watch_detail_parent_book_ignore' => 'Ignorerer via overordnet bok', + 'watch_detail_parent_chapter' => 'Overvåker via overordnet kapittel', + 'watch_detail_parent_chapter_ignore' => 'Ignorerer via overordnet kapittel', ]; diff --git a/lang/nb/errors.php b/lang/nb/errors.php index 09a34a298..a1db2be82 100644 --- a/lang/nb/errors.php +++ b/lang/nb/errors.php @@ -58,7 +58,7 @@ return [ // Pages 'page_draft_autosave_fail' => 'Kunne ikke lagre utkastet, forsikre deg om at du er tilkoblet tjeneren (Har du nettilgang?)', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'Kunne ikke slette sideutkast og hente gjeldende side lagret innhold', 'page_custom_home_deletion' => 'Kan ikke slette en side som er satt som forside.', // Entities @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Feil kastet når du sendte en test-e-post:', + // HTTP errors + 'http_ssr_url_no_match' => 'URLen samsvarer ikke med de konfigurerte SSR-vertene', ]; diff --git a/lang/nb/notifications.php b/lang/nb/notifications.php new file mode 100644 index 000000000..78aad7181 --- /dev/null +++ b/lang/nb/notifications.php @@ -0,0 +1,26 @@ + 'Ny kommentar på siden: :pageName', + 'new_comment_intro' => 'En bruker har kommentert en side i :appName:', + 'new_page_subject' => 'Ny side: :pageName', + 'new_page_intro' => 'En ny side er opprettet i :appName:', + 'updated_page_subject' => 'Oppdatert side: :pageName', + 'updated_page_intro' => 'En side er oppdatert i :appName:', + 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', + + 'detail_page_name' => 'Sidenavn:', + 'detail_commenter' => 'Kommentar fra:', + 'detail_comment' => 'Kommentar:', + 'detail_created_by' => 'Opprettet av:', + 'detail_updated_by' => 'Oppdatert av:', + + 'action_view_comment' => 'Vis kommentar', + 'action_view_page' => 'Se side', + + 'footer_reason' => 'Denne meldingen ble sendt til deg fordi :link dekker denne typen aktivitet for dette elementet.', + 'footer_reason_link' => 'dine varslingsinnstillinger', +]; diff --git a/lang/nb/preferences.php b/lang/nb/preferences.php index bc066d035..16fd8cbd0 100644 --- a/lang/nb/preferences.php +++ b/lang/nb/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Innstillinger', + 'shortcuts' => 'Snarveier', 'shortcuts_interface' => 'Grensesnitt hurtigtaster', 'shortcuts_toggle_desc' => 'Her kan du aktivere eller deaktivere snarveier for tastatur system som brukes til navigasjon og handlinger.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Lagre snarveier', 'shortcuts_overlay_desc' => 'Merk: Når snarveier er aktivert er et hjelperoverlegg tilgjengelig via å trykke "?" som vil fremheve de tilgjengelige snarveiene som for øyeblikket er synlige på skjermen.', 'shortcuts_update_success' => 'Snarvei innstillinger er oppdatert!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Behandle tastatursnarveier du kan bruke for å navigere i systembrukergrensesnittet.', + + 'notifications' => 'Innstillinger for varsling', + 'notifications_desc' => 'Kontroller e-postvarslene du mottar når en bestemt aktivitet utføres i systemet.', + 'notifications_opt_own_page_changes' => 'Varsle ved endringer til sider jeg eier', + 'notifications_opt_own_page_comments' => 'Varsle om kommentarer på sider jeg eier', + 'notifications_opt_comment_replies' => 'Varsle ved svar på mine kommentarer', + 'notifications_save' => 'Lagre innstillinger', + 'notifications_update_success' => 'Varslingsinnstillingene er oppdatert!', + 'notifications_watched' => 'Overvåka & ignorerte elementer', + 'notifications_watched_desc' => ' Nedenfor er elementene som har egendefinerte varslingsinnstillinger i bruk. For å oppdatere innstillingene for disse, se elementet, finn varslingsalternativene i sidepanelet.', + + 'profile_overview_desc' => ' Behandle brukerprofildetaljene dine, inkludert foretrukne språk og autentiseringsalternativer.', +]; diff --git a/lang/nb/settings.php b/lang/nb/settings.php index d4d8b1803..73ee7c607 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Behandle applikasjonsinnstillinger', 'role_export_content' => 'Eksporter innhold', 'role_editor_change' => 'Endre sideredigering', + 'role_notifications' => 'Motta og administrere varslinger', 'role_asset' => 'Eiendomstillatelser', 'roles_system_warning' => 'Vær oppmerksom på at tilgang til noen av de ovennevnte tre tillatelsene kan tillate en bruker å endre sine egne rettigheter eller rettighetene til andre i systemet. Bare tildel roller med disse tillatelsene til pålitelige brukere.', 'role_asset_desc' => 'Disse tillatelsene kontrollerer standard tilgang til eiendelene i systemet. Tillatelser til bøker, kapitler og sider overstyrer disse tillatelsene.', diff --git a/lang/nl/activities.php b/lang/nl/activities.php index 2eedd43d2..633ac888e 100644 --- a/lang/nl/activities.php +++ b/lang/nl/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" is toegevoegd aan je favorieten', 'favourite_remove_notification' => '":name" is verwijderd uit je favorieten', + // Watching + 'watch_update_level_notification' => 'Volg voorkeuren succesvol aangepast', + // Auth 'auth_login' => 'heeft ingelogd', 'auth_register' => 'geregistreerd als nieuwe gebruiker', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'is van prullenbak hersteld', 'recycle_bin_destroy' => 'is van prullenbak verwijderd', - // Other + // Comments 'commented_on' => 'reageerde op', + 'comment_create' => 'heeft opmerking toegevoegd', + 'comment_update' => 'heeft opmerking aangepast', + 'comment_delete' => 'heeft opmerking verwijderd', + + // Other 'permissions_update' => 'wijzigde machtigingen', ]; diff --git a/lang/nl/common.php b/lang/nl/common.php index 8f43f363a..2d310545e 100644 --- a/lang/nl/common.php +++ b/lang/nl/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Verwijder', 'add' => 'Voeg toe', 'configure' => 'Configureer', + 'manage' => 'Beheer', 'fullscreen' => 'Volledig scherm', 'favourite' => 'Favoriet', 'unfavourite' => 'Verwijderen als favoriet', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index 7c35d838f..591b78313 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Boekenplank Machtigingen Bijgewerkt', 'shelves_permissions_active' => 'Machtigingen op Boekenplank Actief', 'shelves_permissions_cascade_warning' => 'De ingestelde machtigingen op deze boekenplank worden niet automatisch toegepast op de boeken van deze boekenplank. Dit is omdat een boek toegekend kan worden op meerdere boekenplanken. De machtigingen van deze boekenplank kunnen echter wel gekopieerd worden naar de boeken van deze boekenplank via de optie hieronder.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => '\'Maak boekenplank\' machtigingen worden enkel gebruikt om machtigingen te kopiëren naar boeken binnenin een boekenplank door gebruik te maken van onderstaande actie. Deze machtigingen laten niet toe om een nieuw boek aan te maken.', 'shelves_copy_permissions_to_books' => 'Kopieer Machtigingen naar Boeken', 'shelves_copy_permissions' => 'Kopieer Machtigingen', 'shelves_copy_permissions_explain' => 'Met deze actie worden de machtigingen van deze boekenplank gekopieërd naar alle boeken van deze boekenplank. Voor je deze actie uitvoert, moet je ervoor zorgen dat alle wijzigingen in de machtigingen van deze boekenplank zijn opgeslagen.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Tekening invoegen', 'pages_md_show_preview' => 'Toon preview', 'pages_md_sync_scroll' => 'Synchroniseer preview scroll', + 'pages_drawing_unsaved' => 'Niet-opgeslagen Tekening Gevonden', + 'pages_drawing_unsaved_confirm' => 'Er zijn niet-opgeslagen tekeninggegevens gevonden van een eerdere mislukte poging om de tekening op te slaan. Wilt u deze niet-opgeslagen tekening herstellen en verder bewerken?', 'pages_not_in_chapter' => 'Deze pagina staat niet in een hoofdstuk', 'pages_move' => 'Pagina verplaatsten', 'pages_copy' => 'Pagina kopiëren', @@ -403,4 +405,28 @@ return [ 'references' => 'Verwijzingen', 'references_none' => 'Er zijn geen verwijzingen naar dit artikel bijgehouden.', 'references_to_desc' => 'Hieronder staan alle gekende pagina\'s in het systeem die naar dit item linken.', + + // Watch Options + 'watch' => 'Volg', + 'watch_title_default' => 'Standaard Voorkeuren', + 'watch_desc_default' => 'Terugkeren naar alleen je standaardvoorkeuren voor meldingen.', + 'watch_title_ignore' => 'Negeer', + 'watch_desc_ignore' => 'Negeer alle meldingen, inclusief die van voorkeuren op gebruikersniveau.', + 'watch_title_new' => 'Nieuwe pagina\'s', + 'watch_desc_new' => 'Geef een melding wanneer er een nieuwe pagina wordt gemaakt binnen dit item.', + 'watch_title_updates' => 'Alle pagina updates', + 'watch_desc_updates' => 'Geef een melding van alle nieuwe pagina\'s en pagina wijzigingen.', + 'watch_desc_updates_page' => 'Geef een melding van pagina wijzigingen.', + 'watch_title_comments' => 'Alle Pagina Updates & Opmerkingen', + 'watch_desc_comments' => 'Geef een melding van alle nieuwe pagina\'s, pagina wijzigingen en nieuwe opmerkingen.', + 'watch_desc_comments_page' => 'Geef een melding van pagina wijzigingen en nieuwe opmerkingen.', + 'watch_change_default' => 'Standaardvoorkeuren voor meldingen wijzigen', + 'watch_detail_ignore' => 'Meldingen negeren', + 'watch_detail_new' => 'Op de uitkijk voor nieuwe pagina\'s', + 'watch_detail_updates' => 'Op de uitkijk voor nieuwe pagina\'s en aanpassingen', + 'watch_detail_comments' => 'Op de uitkijk voor nieuwe pagina\'s, aanpassingen en opmerkingen', + 'watch_detail_parent_book' => 'Op de uitkijk via hogerliggend boek', + 'watch_detail_parent_book_ignore' => 'Aan het negeren via hogerliggend boek', + 'watch_detail_parent_chapter' => 'Op de uitkijk via hogerliggend hoofdstuk', + 'watch_detail_parent_chapter_ignore' => 'Aan het negeren via hogerliggend hoofdstuk', ]; diff --git a/lang/nl/errors.php b/lang/nl/errors.php index 5e8fa5692..6a5308c4f 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Fout opgetreden bij het verzenden van een test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'De URL komt niet overeen met de geconfigureerde toegestane SSR-hosts', ]; diff --git a/lang/nl/notifications.php b/lang/nl/notifications.php new file mode 100644 index 000000000..9e173dff5 --- /dev/null +++ b/lang/nl/notifications.php @@ -0,0 +1,26 @@ + 'Nieuwe opmerking op pagina: :pageName', + 'new_comment_intro' => 'Een gebruiker heeft gereageerd op een pagina in :appName:', + 'new_page_subject' => 'Nieuwe pagina: :pageName', + 'new_page_intro' => 'Een nieuwe pagina is gemaakt in :appName:', + 'updated_page_subject' => 'Aangepaste pagina: :pageName', + 'updated_page_intro' => 'Een pagina werd aangepast in :appName:', + 'updated_page_debounce' => 'Om een stortvloed aan meldingen te voorkomen, zul je een tijdje geen meldingen ontvangen voor verdere bewerkingen van deze pagina door dezelfde redacteur.', + + 'detail_page_name' => 'Pagina Naam:', + 'detail_commenter' => 'Reageerder:', + 'detail_comment' => 'Opmerking:', + 'detail_created_by' => 'Gemaakt Door:', + 'detail_updated_by' => 'Aangepast Door:', + + 'action_view_comment' => 'Bekijk Opmerking', + 'action_view_page' => 'Bekijk Pagina', + + 'footer_reason' => 'Deze melding is naar u verzonden omdat :link dit type activiteit voor dit artikel dekt.', + 'footer_reason_link' => 'je meldingsvoorkeuren', +]; diff --git a/lang/nl/preferences.php b/lang/nl/preferences.php index f587e99dc..3d879332a 100644 --- a/lang/nl/preferences.php +++ b/lang/nl/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Voorkeuren', + 'shortcuts' => 'Snelkoppelingen', 'shortcuts_interface' => 'Toetsencombinaties voor de gebruikersinterface', 'shortcuts_toggle_desc' => 'Hier kunt u toetscombinaties voor de gebruikersinterface in- of uitschakelen voor navigatie en acties.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Sla Toetsencombinaties Op', 'shortcuts_overlay_desc' => 'Opmerking: Wanneer toetsencombinaties zijn ingeschakeld, is een overlay beschikbaar door op "?" te drukken, die de momenteel beschikbare toetscombinaties voor acties op het scherm markeert.', 'shortcuts_update_success' => 'Toetsencombinatievoorkeuren zijn bijgewerkt!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Beheer toetsenbordsnelkoppelingen om door de gebruikersinterface van het systeem te navigeren.', + + 'notifications' => 'Melding Voorkeuren', + 'notifications_desc' => 'Bepaal welke e-mailmeldingen je ontvangt wanneer bepaalde activiteiten in het systeem worden uitgevoerd.', + 'notifications_opt_own_page_changes' => 'Geef melding bij wijzigingen aan pagina\'s waarvan ik de eigenaar ben', + 'notifications_opt_own_page_comments' => 'Geef melding van opmerkingen op pagina\'s waarvan ik de eigenaar ben', + 'notifications_opt_comment_replies' => 'Geef melding van reacties op mijn opmerkingen', + 'notifications_save' => 'Voorkeuren opslaan', + 'notifications_update_success' => 'Voorkeuren voor meldingen zijn bijgewerkt!', + 'notifications_watched' => 'Gevolgde & Genegeerde Items', + 'notifications_watched_desc' => ' Hieronder staan de items waarvoor aangepaste \'Volg\'-voorkeuren zijn toegepast. Om je voorkeuren voor deze items bij te werken, bekijk je het item en zoek je naar de \'Volg\' opties in de zijbalk.', + + 'profile_overview_desc' => ' Beheer de details van je gebruikersprofiel, inclusief de voorkeurstaal en verificatieopties.', +]; diff --git a/lang/nl/settings.php b/lang/nl/settings.php index e66566877..915999f8e 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Beheer app instellingen', 'role_export_content' => 'Exporteer inhoud', 'role_editor_change' => 'Wijzig pagina bewerker', + 'role_notifications' => 'Meldingen ontvangen & beheren', '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.', diff --git a/lang/pl/activities.php b/lang/pl/activities.php index 1a77f93e4..8b35621de 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" został dodany do Twoich ulubionych', 'favourite_remove_notification' => '":name" został usunięty z ulubionych', + // Watching + 'watch_update_level_notification' => 'Ustawienia obserwowania pomyślnie zaktualizowane', + // Auth 'auth_login' => 'zalogował się', 'auth_register' => 'zarejestrowany jako nowy użytkownik', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'przywrócił z kosza', 'recycle_bin_destroy' => 'usunął z kosza', - // Other + // Comments 'commented_on' => 'skomentował', + 'comment_create' => 'dodał komentarz', + 'comment_update' => 'zaktualizował komentarz', + 'comment_delete' => 'usunął komentarz', + + // Other 'permissions_update' => 'zaktualizował uprawnienia', ]; diff --git a/lang/pl/common.php b/lang/pl/common.php index 0de5d7ebc..ca9b46347 100644 --- a/lang/pl/common.php +++ b/lang/pl/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Usuń', 'add' => 'Dodaj', 'configure' => 'Konfiguruj', + 'manage' => 'Zarządzaj', 'fullscreen' => 'Pełny ekran', 'favourite' => 'Ulubione', 'unfavourite' => 'Usuń z ulubionych', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index 557e847c2..4c0423962 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Uprawnienia półki zostały zaktualizowane', 'shelves_permissions_active' => 'Uprawnienia półki są aktywne', 'shelves_permissions_cascade_warning' => 'Uprawnienia na półkach nie są automatycznie nakładane na zawartych w nich książkach. Dzieje się tak dlatego, że książka może istnieć na wielu półkach. Uprawnienia można jednak skopiować do książek podrzędnych, korzystając z opcji znajdującej się poniżej.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Uprawnienia tworzenia półki są używane tylko do kopiowania uprawnień do książek podrzędnych za pomocą poniższej czynności. Nie kontrolują możliwości tworzenia książek.', 'shelves_copy_permissions_to_books' => 'Skopiuj uprawnienia do książek', 'shelves_copy_permissions' => 'Skopiuj uprawnienia', 'shelves_copy_permissions_explain' => 'To spowoduje zastosowanie obecnych ustawień uprawnień tej półki na wszystkich książkach w niej zawartych. Przed aktywacją upewnij się, że wszelkie zmiany w uprawnieniach tej półki zostały zapisane.', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Wstaw rysunek', 'pages_md_show_preview' => 'Pokaż podgląd', 'pages_md_sync_scroll' => 'Synchronizuj przewijanie podglądu', + 'pages_drawing_unsaved' => 'Znaleziono niezapisany rysunek', + 'pages_drawing_unsaved_confirm' => 'Znaleziono niezapisane dane rysowania z poprzedniej nieudanej próby zapisu. Czy chcesz przywrócić i kontynuować edycję tego niezapisanego rysunku?', 'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale', 'pages_move' => 'Przenieś stronę', 'pages_copy' => 'Skopiuj stronę', @@ -268,11 +270,11 @@ return [ 'pages_copy_link' => 'Kopiuj link', 'pages_edit_content_link' => 'Przejdź do sekcji w edytorze', 'pages_pointer_enter_mode' => 'Aktywuj tryb wyboru sekcji', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_pointer_label' => 'Sekcja opcji strony', + 'pages_pointer_permalink' => 'Sekcja odnośnika strony', + 'pages_pointer_include_tag' => 'Sekcja taga inkludującego', + 'pages_pointer_toggle_link' => 'Tryb bezpośredniego linku, naciśnij aby zmienić na tryb tagu do inkludowania', + 'pages_pointer_toggle_include' => 'Tryb tagu do inkludowania, naciśnij aby zmienić na tryb bezpośredniego linku', 'pages_permissions_active' => 'Uprawnienia strony są aktywne', 'pages_initial_revision' => 'Pierwsze wydanie', 'pages_references_update_revision' => 'Automatyczna aktualizacja wewnętrznych linków', @@ -403,4 +405,28 @@ return [ 'references' => 'Odniesienia', 'references_none' => 'Brak śledzonych odwołań do tego elementu.', 'references_to_desc' => 'Poniżej znajdują się wszystkie znane strony w systemie, które odnoszą się do tego elementu.', + + // Watch Options + 'watch' => 'Obserwuj', + 'watch_title_default' => 'Domyślne ustawienia', + 'watch_desc_default' => 'Przywróć do tylko domyślnych ustawień powiadomień.', + 'watch_title_ignore' => 'Ignoruj', + 'watch_desc_ignore' => 'Ignoruj wszystkie powiadomienia, w tym te z preferencji użytkownika.', + 'watch_title_new' => 'Nowe strony', + 'watch_desc_new' => 'Powiadom o utworzeniu nowej strony w tym elemencie.', + 'watch_title_updates' => 'Wszystkie aktualizacje strony', + 'watch_desc_updates' => 'Powiadom o wszystkich nowych stronach i zmianach strony.', + 'watch_desc_updates_page' => 'Powiadom o wszystkich zmianach strony.', + 'watch_title_comments' => 'Wszystkie aktualizacje strony i komentarze', + 'watch_desc_comments' => 'Powiadom o wszystkich nowych stronach, zmianach na stronie i nowych komentarzach.', + 'watch_desc_comments_page' => 'Powiadom o zmianach strony i nowych komentarzach.', + 'watch_change_default' => 'Zmień domyślne ustawienia powiadomień', + 'watch_detail_ignore' => 'Ignorowanie powiadomień', + 'watch_detail_new' => 'Obserwowanie nowych stron', + 'watch_detail_updates' => 'Obserwowanie nowych stron i aktualizacji', + 'watch_detail_comments' => 'Obserwowanie nowych stron, aktualizacji i komentarzy', + 'watch_detail_parent_book' => 'Obserwowanie przez książkę nadrzędną', + 'watch_detail_parent_book_ignore' => 'Ignorowanie przez książkę nadrzędną', + 'watch_detail_parent_chapter' => 'Obserwowanie przez rozdział nadrzędny', + 'watch_detail_parent_chapter_ignore' => 'Ignorowanie przez rozdział nadrzędny', ]; diff --git a/lang/pl/errors.php b/lang/pl/errors.php index 9c54a2bca..9602dc603 100644 --- a/lang/pl/errors.php +++ b/lang/pl/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Błąd podczas wysyłania testowej wiadomości e-mail:', + // HTTP errors + 'http_ssr_url_no_match' => 'Adres URL nie pasuje do skonfigurowanych dozwolonych hostów SSR', ]; diff --git a/lang/pl/notifications.php b/lang/pl/notifications.php new file mode 100644 index 000000000..d3f86d02d --- /dev/null +++ b/lang/pl/notifications.php @@ -0,0 +1,26 @@ + 'Nowy komentarz na stronie: :pageName', + 'new_comment_intro' => 'Użytkownik skomentował stronę w :appName:', + 'new_page_subject' => 'Nowa strona: :pageName', + 'new_page_intro' => 'Nowa strona została utworzona w :appName:', + 'updated_page_subject' => 'Zaktualizowano stronę: :pageName', + 'updated_page_intro' => 'Strona została zaktualizowana w :appName:', + 'updated_page_debounce' => 'Aby zapobiec nadmiarowi powiadomień, przez jakiś czas nie będziesz otrzymywać powiadomień o dalszych edycjach tej strony przez tego samego edytora.', + + 'detail_page_name' => 'Nazwa strony:', + 'detail_commenter' => 'Skomentował:', + 'detail_comment' => 'Komentarz:', + 'detail_created_by' => 'Utworzono przez:', + 'detail_updated_by' => 'Zaktualizowano przez:', + + 'action_view_comment' => 'Pokaż komentarz', + 'action_view_page' => 'Wyświetl stronę', + + 'footer_reason' => 'To powiadomienie zostało wysłane do Ciebie, ponieważ :link obejmuje ten typ aktywności dla tego elementu.', + 'footer_reason_link' => 'ustawienia powiadomień', +]; diff --git a/lang/pl/preferences.php b/lang/pl/preferences.php index df34b21f7..af24aaed2 100644 --- a/lang/pl/preferences.php +++ b/lang/pl/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferencje', + 'shortcuts' => 'Skróty', 'shortcuts_interface' => 'Interfejs Skrótów Klawiszowych', 'shortcuts_toggle_desc' => 'Tutaj możesz włączyć lub wyłączyć interfejs skrótów klawiszowych używanych do nawigacji i akcji.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Zapisz skróty', 'shortcuts_overlay_desc' => 'Uwaga: Gdy skróty są włączone, przez naciśnięcie "?" może być otworzona nakładka pomocnicza, która podświetli dostępne skróty dla akcji widocznych obecnie na ekranie.', 'shortcuts_update_success' => 'Ustawienia skrótów zostały zaktualizowane!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Zarządzaj skrótami klawiaturowymi, które możesz użyć do nawigacji interfejsu użytkownika systemu.', + + 'notifications' => 'Preferencje powiadomień', + 'notifications_desc' => 'Kontroluj otrzymywane powiadomienia e-mail, gdy określona aktywność jest wykonywana w systemie.', + 'notifications_opt_own_page_changes' => 'Powiadom o zmianach na stronach, których jestem właścicielem', + 'notifications_opt_own_page_comments' => 'Powiadom o komentarzach na stronach, których jestem właścicielem', + 'notifications_opt_comment_replies' => 'Powiadom o odpowiedziach na moje komentarze', + 'notifications_save' => 'Zapisz preferencje', + 'notifications_update_success' => 'Preferencje powiadomień zostały zaktualizowane!', + 'notifications_watched' => 'Obserwowane i ignorowane elementy', + 'notifications_watched_desc' => ' Poniżej znajdują się elementy, które mają własne preferencje obserwowania. Aby zaktualizować swoje preferencje, zobacz dany element, a następnie znajdź opcje obserwowania na pasku bocznym.', + + 'profile_overview_desc' => ' Zarządzaj szczegółami swojego profilu użytkownika, w tym preferowanym językiem i opcjami uwierzytelniania.', +]; diff --git a/lang/pl/settings.php b/lang/pl/settings.php index f30e81a06..32cda6e64 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji', 'role_export_content' => 'Eksportuj zawartość', 'role_editor_change' => 'Zmień edytor strony', + 'role_notifications' => 'Odbieranie i zarządzanie powiadomieniami', '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.', diff --git a/lang/pt/activities.php b/lang/pt/activities.php index 05e5b5fef..18645acf9 100644 --- a/lang/pt/activities.php +++ b/lang/pt/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" foi adicionado aos seus favoritos', 'favourite_remove_notification' => '":name" foi removido dos seus favoritos', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'sessão iniciada', 'auth_register' => 'registado como novo utilizador', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restaurado da reciclagem', 'recycle_bin_destroy' => 'removido da reciclagem', - // Other + // Comments 'commented_on' => 'comentado a', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'permissões atualizadas', ]; diff --git a/lang/pt/common.php b/lang/pt/common.php index 39240a0f8..dc8582ce6 100644 --- a/lang/pt/common.php +++ b/lang/pt/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remover', 'add' => 'Adicionar', 'configure' => 'Configurar', + 'manage' => 'Manage', 'fullscreen' => 'Ecrã completo', 'favourite' => 'Favorito', 'unfavourite' => 'Retirar Favorito', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index 30e4ec35e..3cf385726 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Inserir Desenho', 'pages_md_show_preview' => 'Mostrar pré-visualização', 'pages_md_sync_scroll' => 'Sincronizar pré-visualização', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'A página não está dentro de um capítulo', 'pages_move' => 'Mover Página', 'pages_copy' => 'Copiar Página', @@ -403,4 +405,28 @@ return [ 'references' => 'Referências', 'references_none' => 'Não há referências registadas para este item.', 'references_to_desc' => 'Abaixo estão todas as páginas conhecidas do sistema que vinculam este item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/pt/errors.php b/lang/pt/errors.php index f6ecceb07..c157112ab 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro lançado ao enviar um e-mail de teste:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/pt/notifications.php b/lang/pt/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/pt/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/pt/preferences.php b/lang/pt/preferences.php index 4784aefe9..e0c1d9c78 100644 --- a/lang/pt/preferences.php +++ b/lang/pt/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Atalhos', 'shortcuts_interface' => 'Atalhos de Teclado', 'shortcuts_toggle_desc' => 'Aqui pode ativar ou desativar os atalhos de teclado do sistema, usados para navegação e ações.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Salvar Atalhos', 'shortcuts_overlay_desc' => 'Nota: Quando os atalhos estão ativados, um balão de ajuda ficará disponível pressionando "?" destacando os atalhos disponíveis para ações atualmente visíveis na tela.', 'shortcuts_update_success' => 'As suas preferências de atalhos foram guardadas!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/pt/settings.php b/lang/pt/settings.php index aab5664b2..1ce3751ac 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gerir as configurações da aplicação', 'role_export_content' => 'Exportar conteúdo', 'role_editor_change' => 'Alterar editor de página', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um utilizador altere os seus próprios privilégios ou privilégios de outros no sistema. Apenas atribua cargos com essas permissões a utilizadores de confiança.', 'role_asset_desc' => 'Estas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por estas permissões.', diff --git a/lang/pt_BR/activities.php b/lang/pt_BR/activities.php index b06dc1430..bc87ebf5e 100644 --- a/lang/pt_BR/activities.php +++ b/lang/pt_BR/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'restaurou a página', 'page_restore_notification' => 'Página restaurada com sucesso', 'page_move' => 'moveu a página', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Página movida com sucesso', // Chapters 'chapter_create' => 'criou o capítulo', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'excluiu o capítulo', 'chapter_delete_notification' => 'Capítulo excluída com sucesso', 'chapter_move' => 'moveu o capítulo', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Capítulo excluído com sucesso', // Books 'book_create' => 'criou o livro', @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Prateleira excluída com sucesso', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'revisão restaurada', + 'revision_delete' => 'revisão excluída', + 'revision_delete_notification' => 'Revisão excluída com sucesso', // Favourites 'favourite_add_notification' => '":name" foi adicionada aos seus favoritos', 'favourite_remove_notification' => '":name" foi removida dos seus favoritos', + // Watching + 'watch_update_level_notification' => 'Preferências de Observação atualizadas com sucesso', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'conectado', + 'auth_register' => 'registrado como novo usuário', + 'auth_password_reset_request' => 'redefinir senha do usuário solicitado', + 'auth_password_reset_update' => 'redefinir senha do usuário', + 'mfa_setup_method' => 'método MFA configurado', 'mfa_setup_method_notification' => 'Método de multi-fatores configurado com sucesso', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'Método MFA removido', 'mfa_remove_method_notification' => 'Método de multi-fatores removido com sucesso', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'configurações atualizadas', + 'settings_update_notification' => 'Configurações atualizadas com sucesso', + 'maintenance_action_run' => 'Ação de manutenção executada', // Webhooks 'webhook_create' => 'webhook criado', @@ -82,35 +85,40 @@ return [ 'webhook_delete_notification' => 'Webhook excluido com sucesso', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'usuário criado', + 'user_create_notification' => 'Usuário criado com sucesso', + 'user_update' => 'usuário atualizado', 'user_update_notification' => 'Usuário atualizado com sucesso', - 'user_delete' => 'deleted user', + 'user_delete' => 'usuário excluído', 'user_delete_notification' => 'Usuário removido com sucesso', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'token de api criado', + 'api_token_create_notification' => 'Token de API criado com sucesso', + 'api_token_update' => 'token de API atualizado', + 'api_token_update_notification' => 'Token de API atualizado com sucesso', + 'api_token_delete' => 'token de api excluído', + 'api_token_delete_notification' => 'Token de API excluído com sucesso', // Roles - 'role_create' => 'created role', + 'role_create' => 'função criada', 'role_create_notification' => 'Perfil criado com sucesso', - 'role_update' => 'updated role', + 'role_update' => 'função atualizada', 'role_update_notification' => 'Perfil atualizado com sucesso', - 'role_delete' => 'deleted role', + 'role_delete' => 'Excluir papel', 'role_delete_notification' => 'Perfil excluído com sucesso', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'lixeira esvaziada', + 'recycle_bin_restore' => 'restaurado da lixeira', + 'recycle_bin_destroy' => 'removido da lixeira', + + // Comments + 'commented_on' => 'comentou em', + 'comment_create' => 'Adicionou comentário', + 'comment_update' => 'Atualizar descrição', + 'comment_delete' => 'Comentário deletado', // Other - 'commented_on' => 'comentou em', 'permissions_update' => 'atualizou permissões', ]; diff --git a/lang/pt_BR/common.php b/lang/pt_BR/common.php index 08f40e55e..7ab570580 100644 --- a/lang/pt_BR/common.php +++ b/lang/pt_BR/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Cancelar', - 'close' => 'Close', + 'close' => 'Fechar', 'confirm' => 'Confirmar', 'back' => 'Voltar', 'save' => 'Salvar', @@ -42,6 +42,7 @@ return [ 'remove' => 'Remover', 'add' => 'Adicionar', 'configure' => 'Configurar', + 'manage' => 'Administrar', 'fullscreen' => 'Tela cheia', 'favourite' => 'Favoritos', 'unfavourite' => 'Remover dos Favoritos', diff --git a/lang/pt_BR/components.php b/lang/pt_BR/components.php index fbd83bce8..d91adf268 100644 --- a/lang/pt_BR/components.php +++ b/lang/pt_BR/components.php @@ -6,8 +6,8 @@ return [ // Image Manager 'image_select' => 'Selecionar Imagem', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', + 'image_list' => 'Lista de imagens', + 'image_details' => 'Detalhes da Imagem', 'image_upload' => 'Fazer upload de imagem', 'image_intro' => 'Aqui você pode selecionar e gerenciar imagens que foram previamente enviadas para o sistema.', 'image_intro_upload' => 'Faça upload de uma imagem arrastando um arquivo de imagem para esta janela, ou usando o botão "Fazer upload de imagem" acima.', @@ -17,9 +17,9 @@ return [ 'image_page_title' => 'visualizar imagens relacionadas a essa página', 'image_search_hint' => 'Pesquisar imagem por nome', 'image_uploaded' => 'Adicionada em :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'Enviado por :userName', + 'image_uploaded_to' => 'Enviado para :pageLink', + 'image_updated' => 'Atualizou :updateDate', 'image_load_more' => 'Carregar Mais', 'image_image_name' => 'Nome da Imagem', 'image_delete_used' => 'Essa imagem é usada nas páginas abaixo.', @@ -32,8 +32,8 @@ return [ 'image_upload_success' => 'Upload de imagem efetuado com sucesso', 'image_update_success' => 'Detalhes da imagem atualizados com sucesso', 'image_delete_success' => 'Imagem excluída com sucesso', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', + 'image_replace' => 'Substituir imagem', + 'image_replace_success' => 'Arquivo de imagem atualizado com sucesso', // Code Editor 'code_editor' => 'Editar Código', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 5d3c47c19..4498cd2bd 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permissões de prateleira atualizadas', 'shelves_permissions_active' => 'Permissões de prateleira ativas', 'shelves_permissions_cascade_warning' => 'As permissões nas prateleiras não são automaticamente em cascata para os livros contidos. Isso ocorre porque um livro pode existir em várias prateleiras. No entanto, as permissões podem ser copiadas para livros filhos usando a opção encontrada abaixo.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'As permissões de criação de prateleira são usadas apenas para copiar livros filhos usando a ação abaixo. Eles não controlam a capacidade de criar livros.', 'shelves_copy_permissions_to_books' => 'Copiar Permissões para Livros', 'shelves_copy_permissions' => 'Copiar Permissões', 'shelves_copy_permissions_explain' => 'Isso aplicará as configurações de permissão atuais desta estante a todos os livros contidos nela. Antes de ativar, verifique se todas as alterações nas permissões desta prateleira foram salvas.', @@ -214,7 +214,7 @@ return [ 'pages_editing_page' => 'Editando Página', 'pages_edit_draft_save_at' => 'Rascunho salvo em ', 'pages_edit_delete_draft' => 'Excluir Rascunho', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => 'Tem certeza que deseja excluir as alterações nas páginas de rascunho? Todas as suas alterações, desde o último salvamento completo, serão perdidas e o editor será atualizado com o último estado de salvamento da página.', 'pages_edit_discard_draft' => 'Descartar Rascunho', 'pages_edit_switch_to_markdown' => 'Alternar para o Editor de Markdown', 'pages_edit_switch_to_markdown_clean' => '(Conteúdo Limpo)', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Inserir Desenho', 'pages_md_show_preview' => 'Mostrar pré-visualização', 'pages_md_sync_scroll' => 'Sincronizar pré-visualização', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Página não está dentro de um capítulo', 'pages_move' => 'Mover Página', 'pages_copy' => 'Copiar Página', @@ -266,13 +268,13 @@ return [ 'pages_revisions_restore' => 'Restaurar', 'pages_revisions_none' => 'Essa página não tem revisões', 'pages_copy_link' => 'Copiar Link', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_edit_content_link' => 'Ir para a seção do editor', + 'pages_pointer_enter_mode' => 'Entrar em modo de seleção de seção', + 'pages_pointer_label' => 'Opções de Seção de Página', + 'pages_pointer_permalink' => 'Seção de Página Permalink', + 'pages_pointer_include_tag' => 'Seção de Página Incluir Tag', + 'pages_pointer_toggle_link' => 'Modo permalink, pressione para mostrar a tag incluída', + 'pages_pointer_toggle_include' => 'Incluir o modo tag, pressione para mostrar permalink', 'pages_permissions_active' => 'Permissões de Página Ativas', 'pages_initial_revision' => 'Publicação Inicial', 'pages_references_update_revision' => 'Atualização automática do sistema de links internos', @@ -287,8 +289,8 @@ return [ 'time_b' => 'nos últimos :minCount minutos', 'message' => ':start :time. Tome cuidado para não sobrescrever atualizações de outras pessoas!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'Rascunho descartado! O editor foi atualizado com o conteúdo da página atual', + 'pages_draft_deleted' => 'Rascunho excluído! O editor foi atualizado com o conteúdo da página atual', 'pages_specific' => 'Página Específica', 'pages_is_template' => 'Modelo de Página', @@ -366,13 +368,13 @@ return [ 'comment_new' => 'Novo Comentário', 'comment_created' => 'comentado :createDiff', 'comment_updated' => 'Editado :updateDiff por :username', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Atualizado', 'comment_deleted_success' => 'Comentário removido', 'comment_created_success' => 'Comentário adicionado', 'comment_updated_success' => 'Comentário editado', 'comment_delete_confirm' => 'Você tem certeza de que deseja excluir este comentário?', 'comment_in_reply_to' => 'Em resposta à :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'Aqui estão os comentários que foram deixados nesta página. Comentários podem ser adicionados e gerenciados ao visualizar a página salva.', // Revision 'revision_delete_confirm' => 'Tem certeza de que deseja excluir esta revisão?', @@ -403,4 +405,28 @@ return [ 'references' => 'Referências', 'references_none' => 'Não há referências rastreadas para este item.', 'references_to_desc' => 'Abaixo estão todas as páginas conhecidas no sistema que vinculam a este item.', + + // Watch Options + 'watch' => 'Acompanhar', + 'watch_title_default' => 'Preferências padrão', + 'watch_desc_default' => 'Reverter o acompanhamento apenas para suas preferências de notificação padrão.', + 'watch_title_ignore' => 'Ignorar', + 'watch_desc_ignore' => 'Ignorar todas as notificações, incluindo as de preferências de nível de usuário.', + 'watch_title_new' => 'Novas Páginas', + 'watch_desc_new' => 'Notificar quando qualquer nova página for criada dentro deste item.', + 'watch_title_updates' => 'Todas as atualizações da página', + 'watch_desc_updates' => 'Notificar sobre todas as novas páginas e alterações na página.', + 'watch_desc_updates_page' => 'Notificar sobre todas as alterações da página.', + 'watch_title_comments' => 'Todas as atualizações e comentários da página', + 'watch_desc_comments' => 'Notificar sobre todas as novas páginas, alterações de página e novos comentários.', + 'watch_desc_comments_page' => 'Notificar sobre alterações na página e novos comentários.', + 'watch_change_default' => 'Alterar preferências padrão de notificação', + 'watch_detail_ignore' => 'Ignorando notificações', + 'watch_detail_new' => 'Acompanhando para novas páginas', + 'watch_detail_updates' => 'Acompanhando novas páginas e atualizações', + 'watch_detail_comments' => 'Acompanhando novas páginas, atualizações e comentários', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php index b1fb6176d..63defbda9 100644 --- a/lang/pt_BR/errors.php +++ b/lang/pt_BR/errors.php @@ -49,7 +49,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Um erro aconteceu enquanto o servidor tentava efetuar o upload da imagem', 'image_upload_type_error' => 'O tipo de imagem que está sendo enviada é inválido', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', + 'image_upload_replace_type' => 'Substituições de arquivos de imagem devem ser do mesmo tipo', 'drawing_data_not_found' => 'Dados de desenho não puderam ser carregados. Talvez o arquivo de desenho não exista mais ou você não tenha permissão para acessá-lo.', // Attachments @@ -58,7 +58,7 @@ return [ // Pages 'page_draft_autosave_fail' => 'Falha ao tentar salvar o rascunho. Certifique-se que a conexão de internet está funcional antes de tentar salvar essa página', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'Falha ao excluir o rascunho da página e buscar conteúdo salvo na página atual', 'page_custom_home_deletion' => 'Não é possível excluir uma página que está definida como página inicial', // Entities @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro encontrado ao enviar um e-mail de teste:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/pt_BR/notifications.php b/lang/pt_BR/notifications.php new file mode 100644 index 000000000..2c315d4f1 --- /dev/null +++ b/lang/pt_BR/notifications.php @@ -0,0 +1,26 @@ + 'Novo comentário na página: :pageName', + 'new_comment_intro' => 'Um usuário comentou em uma página de :appName:', + 'new_page_subject' => 'Nova página: :pageName', + 'new_page_intro' => 'Uma nova página foi criada em :appName:', + 'updated_page_subject' => 'Página atualizada: :pageName', + 'updated_page_intro' => 'Uma página foi atualizada em :appName:', + 'updated_page_debounce' => 'Para prevenir notificações em massa, por enquanto notificações não serão enviadas para você para próximas edições nessa página pelo mesmo editor.', + + 'detail_page_name' => 'Nome da Página:', + 'detail_commenter' => 'Comentador:', + 'detail_comment' => 'Comentário:', + 'detail_created_by' => 'Criado por: ', + 'detail_updated_by' => 'Atualizado por:', + + 'action_view_comment' => 'Ver Comentário', + 'action_view_page' => 'Ver Página', + + 'footer_reason' => 'Essa notificação foi enviada para você porque :link engloba esse tipo de atividade para este item.', + 'footer_reason_link' => 'suas preferências de notificação', +]; diff --git a/lang/pt_BR/preferences.php b/lang/pt_BR/preferences.php index a10e3ddab..ffcce6c8e 100644 --- a/lang/pt_BR/preferences.php +++ b/lang/pt_BR/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferências', + 'shortcuts' => 'Atalhos', 'shortcuts_interface' => 'Atalhos de Teclado da Interface', 'shortcuts_toggle_desc' => 'Aqui você pode habilitar ou desabilitar os atalhos da interface do sistema de teclado, usados para navegação e ações.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Salvar Atalhos', 'shortcuts_overlay_desc' => 'Observação: quando os atalhos estão ativados, uma sobreposição auxiliar está disponível pressionando "?" que destacará os atalhos disponíveis para ações atualmente visíveis na tela.', 'shortcuts_update_success' => 'As preferências de atalho foram atualizadas!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gerencie os atalhos de teclado que você pode usar para navegar na interface de usuário do sistema.', + + 'notifications' => 'Preferências de notificação', + 'notifications_desc' => 'Controle as notificações por e-mail que você recebe quando uma determinada atividade é executada no sistema.', + 'notifications_opt_own_page_changes' => 'Notificar quando houver alterações em páginas que eu possuo', + 'notifications_opt_own_page_comments' => 'Notificar comentários nas páginas que eu possuo', + 'notifications_opt_comment_replies' => 'Notificar ao responder aos meus comentários', + 'notifications_save' => 'Salvar Preferências', + 'notifications_update_success' => 'Preferências de notificação foram atualizadas!', + 'notifications_watched' => 'Itens assistidos e ignorados', + 'notifications_watched_desc' => ' Abaixo estão os itens que possuem preferências de relógio personalizadas aplicadas. Para atualizar suas preferências para estes, veja o item e encontre as opções de relógio na barra lateral.', + + 'profile_overview_desc' => ' Gerencie os detalhes do seu perfil de usuário, incluindo o idioma preferido e opções de autenticação.', +]; diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index e0829c45d..de5fda7bf 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gerenciar configurações da aplicação', 'role_export_content' => 'Exportar conteúdo', 'role_editor_change' => 'Alterar página de edição', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um usuário altere seus próprios privilégios ou privilégios de outros usuários no sistema. Apenas atribua cargos com essas permissões para usuários confiáveis.', 'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.', diff --git a/lang/ro/activities.php b/lang/ro/activities.php index 257d626c3..4caafab05 100644 --- a/lang/ro/activities.php +++ b/lang/ro/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" a fost adăugat la favorite', 'favourite_remove_notification' => '":name" a fost eliminat din favorite', + // Watching + 'watch_update_level_notification' => 'Preferințele de urmărire actualizate cu succes', + // Auth 'auth_login' => 'autentificat', 'auth_register' => 'înregistrat ca utilizator nou', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restaurat din coșul de gunoi', 'recycle_bin_destroy' => 'eliminat din coșul de gunoi', - // Other + // Comments 'commented_on' => 'a comentat la', + 'comment_create' => 'comentariu adăugat', + 'comment_update' => 'comentariu actualizat', + 'comment_delete' => 'comentariu șters', + + // Other 'permissions_update' => 'a actualizat permisiunile', ]; diff --git a/lang/ro/common.php b/lang/ro/common.php index 3b25eb386..142700fa7 100644 --- a/lang/ro/common.php +++ b/lang/ro/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Elimină', 'add' => 'Adaugă', 'configure' => 'Configurează', + 'manage' => 'Gestionează', 'fullscreen' => 'Ecran complet', 'favourite' => 'Adaugă la favorite', 'unfavourite' => 'Șterge de la favorite', diff --git a/lang/ro/components.php b/lang/ro/components.php index e252078b8..27c35440b 100644 --- a/lang/ro/components.php +++ b/lang/ro/components.php @@ -10,7 +10,7 @@ return [ 'image_details' => 'Detalii imagine', 'image_upload' => 'Încarcă imaginea', 'image_intro' => 'Aici puteţi selecta şi gestiona imaginile care au fost încărcate anterior în sistem.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_intro_upload' => 'Încărcați o imagine nouă trăgând o imagine în această fereastră sau utilizând butonul "Încărcați Imaginea" de mai sus.', 'image_all' => 'Tot', 'image_all_title' => 'Vezi toate imaginile', 'image_book_title' => 'Vezi imaginile încărcate în această carte', diff --git a/lang/ro/entities.php b/lang/ro/entities.php index cce7cb290..ff074f93a 100644 --- a/lang/ro/entities.php +++ b/lang/ro/entities.php @@ -104,7 +104,7 @@ return [ 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', 'shelves_permissions' => 'Permisiuni raft', 'shelves_permissions_updated' => 'Permisiunile raftului au fost actualizate', - 'shelves_permissions_active' => 'Shelf Permissions Active', + 'shelves_permissions_active' => 'Permisiuni raft active', 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', 'shelves_copy_permissions_to_books' => 'Copiază permisiunile către cărți', @@ -157,9 +157,9 @@ return [ 'books_sort_move_prev_book' => 'Move to Previous Book', 'books_sort_move_next_book' => 'Move to Next Book', 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', + 'books_sort_move_next_chapter' => 'Mergi la următorul capitol', + 'books_sort_move_book_start' => 'Sari la începutul cârtii', + 'books_sort_move_book_end' => 'Sari la sfârșitul cârtii', 'books_sort_move_before_chapter' => 'Move to Before Chapter', 'books_sort_move_after_chapter' => 'Move to After Chapter', 'books_copy' => 'Copiază cartea', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Inserează desen', 'pages_md_show_preview' => 'Arată previzualizarea', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Pagina nu este într-un capitol', 'pages_move' => 'Mută pagina', 'pages_copy' => 'Copiază pagina', @@ -403,4 +405,28 @@ return [ 'references' => 'Referințe', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'Pagina Nouă', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'Toate actualizările paginii', + 'watch_desc_updates' => 'Notifică atunci când o pagină este editată sau creată.', + 'watch_desc_updates_page' => 'Notifică la toate modificările paginii.', + 'watch_title_comments' => 'Toate actualizările și comentariile paginii', + 'watch_desc_comments' => 'Notifică la toate paginile noi, editările de pagină și comentariile noi.', + 'watch_desc_comments_page' => 'Notifică la toate paginile noi, editările de pagină și comentariile noi.', + 'watch_change_default' => 'Schimbă preferințele implicite de notificare', + 'watch_detail_ignore' => 'Se ignoră notificările', + 'watch_detail_new' => 'Urmărire pagini noi', + 'watch_detail_updates' => 'Urmărire pagini noi şi actualizări', + 'watch_detail_comments' => 'Urmărire pagini noi, actualizări și comentarii', + 'watch_detail_parent_book' => 'Se uită prin cartea părinte', + 'watch_detail_parent_book_ignore' => 'Ignorare prin intermediul cărţii părinte', + 'watch_detail_parent_chapter' => 'Urmărire prin capitolul părinte', + 'watch_detail_parent_chapter_ignore' => 'Urmărire prin capitolul părinte', ]; diff --git a/lang/ro/errors.php b/lang/ro/errors.php index da0c2dd7a..1a1020e21 100644 --- a/lang/ro/errors.php +++ b/lang/ro/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Eroare la trimiterea unui e-mail de test:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ro/notifications.php b/lang/ro/notifications.php new file mode 100644 index 000000000..26a901575 --- /dev/null +++ b/lang/ro/notifications.php @@ -0,0 +1,26 @@ + 'Comentariu nou pe pagina: :pageName', + 'new_comment_intro' => 'Un utilizator a comentat pe o pagină în :appName:', + 'new_page_subject' => 'Pagină nouă: :pageName', + 'new_page_intro' => 'O nouă pagină a fost creată în :appName:', + 'updated_page_subject' => 'Pagina actualizată: :pageName', + 'updated_page_intro' => 'O nouă pagină a fost creată în :appName:', + 'updated_page_debounce' => 'Pentru a preveni notificări în masă, pentru un timp nu veți primi notificări suplimentare la această pagină de către același editor.', + + 'detail_page_name' => 'Nume pagină:', + 'detail_commenter' => 'Cine a comentat:', + 'detail_comment' => 'Comentariu:', + 'detail_created_by' => 'Creat de:', + 'detail_updated_by' => 'Actualizat de:', + + 'action_view_comment' => 'Vizualizați comentariul', + 'action_view_page' => 'Vezi pagina', + + 'footer_reason' => 'Această notificare ți-a fost trimisă deoarece :link acoperă acest tip de activitate pentru acest articol.', + 'footer_reason_link' => 'preferințele dvs. de notificare', +]; diff --git a/lang/ro/preferences.php b/lang/ro/preferences.php index 6c4b0fc35..db41dbd51 100644 --- a/lang/ro/preferences.php +++ b/lang/ro/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferințe', + 'shortcuts' => 'Scurtături', 'shortcuts_interface' => 'Comenzi rapide interfață', 'shortcuts_toggle_desc' => 'Aici puteți activa sau dezactiva scurtăturile interfeței folosite pentru navigare și acțiuni.', @@ -13,6 +15,19 @@ return [ 'shortcuts_section_navigation' => 'Navigare', 'shortcuts_section_actions' => 'Acțiuni comune', 'shortcuts_save' => 'Salvează scurtăturile', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', + 'shortcuts_overlay_desc' => 'Notă: Când comenzile rapide sunt activate popup de ajutor este disponibilă prin apăsarea "?" care va evidenția scurtăturile disponibile pentru acțiunile vizibile în prezent pe ecran.', 'shortcuts_update_success' => 'Preferințele dumneavoastră au fost actualizate!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Gestionați scurtăturile de tastatură pe care le puteți utiliza pentru a naviga prin interfața.', + + 'notifications' => 'Preferințe de notificare', + 'notifications_desc' => 'Controlați notificările prin e-mail pe care le primiți atunci când o anumită activitate este efectuată în sistem.', + 'notifications_opt_own_page_changes' => 'Notifică la comentarii pe paginile pe care le dețin', + 'notifications_opt_own_page_comments' => 'Notifică la comentarii pe paginile pe care le dețin', + 'notifications_opt_comment_replies' => 'Notifică la răspunsurile la comentariile mele', + 'notifications_save' => 'Salvează Preferințe', + 'notifications_update_success' => 'Preferințele de notificare au fost actualizate!', + 'notifications_watched' => 'Articole urmărite și ignorate', + 'notifications_watched_desc' => ' Mai jos sunt elementele care au fost aplicate preferințe personalizate. Pentru a actualiza preferințele pentru acestea, vizualizați elementul și apoi găsiți opțiunile de ceas în bara laterală.', + + 'profile_overview_desc' => ' Gestionează detaliile profilului tău de utilizator, inclusiv opțiunile preferate de limbă și autentificare.', +]; diff --git a/lang/ro/settings.php b/lang/ro/settings.php index 61d1d8e74..393baaa50 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -32,9 +32,9 @@ return [ 'app_custom_html_desc' => 'Orice conținut adăugat aici va fi inserat în partea de jos a secțiunii a fiecărei pagini. Acest lucru este util pentru a suprascrie stilurile sau pentru a adăuga cod analitic.', 'app_custom_html_disabled_notice' => 'Conținutul headerului HTML personalizat este dezactivat pe această pagină de setări pentru a asigura că modificările pot fi inversate.', 'app_logo' => 'Logo aplicație', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', + 'app_logo_desc' => 'Acest lucru este folosit în bara de antet a aplicației, printre alte zone. Această imagine ar trebui să fie de 86px în înălțime. Imaginile mari vor fi scalate în jos.', 'app_icon' => 'Iconiță aplicație', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_icon_desc' => 'Această pictogramă este utilizată pentru tab-urile din browser și pictogramele de comenzi rapide. Aceasta ar trebui să fie o imagine PNG pătrată de 256px.', 'app_homepage' => 'Pagina principală a aplicației', 'app_homepage_desc' => 'Selectează o vizualizare pentru a afișa pe prima pagină în loc de vizualizarea implicită. Permisiunile paginii sunt ignorate pentru paginile selectate.', 'app_homepage_select' => 'Selectează o pagină', @@ -48,9 +48,9 @@ return [ 'app_disable_comments_desc' => 'Dezactivează comentariile pentru toate paginile aplicației.
Comentariile existente nu sunt afișate.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', + 'color_scheme' => 'Schema de culori a aplicației', + 'color_scheme_desc' => 'Setați culorile pe care să le utilizați în interfață. Culorile pot fi configurate separat pentru modurile întuneric şi lumină pentru a se potrivi cel mai bine cu tema şi a asigura lizibilitatea.', + 'ui_colors_desc' => 'Setaţi culoarea primară a aplicaţiei şi culoarea implicită a link-ului. Culoarea primară este utilizată în principal pentru banner-ul antet, butoane şi decoraţiunile interfeţei. Culoarea implicită a link-ului este utilizată pentru link-uri și acțiuni bazate pe text, atât în conținutul scris, cât și în interfața aplicației.', 'app_color' => 'Culoare primară', 'link_color' => 'Culoare link implicită', 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', @@ -137,7 +137,7 @@ return [ 'roles' => 'Roluri', 'role_user_roles' => 'Roluri utilizator', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_users_assigned' => ':count utilizator atribuibil:count utilizatori alocați', 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Utilizator alocat', 'roles_permissions_provided' => 'Permisiuni furnizate', @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Gestionează setările aplicației', 'role_export_content' => 'Exportă conținut', 'role_editor_change' => 'Schimbă editorul de pagină', + 'role_notifications' => 'Primire și gestionare notificări', 'role_asset' => 'Permisiuni active', 'roles_system_warning' => 'Fi conștient de faptul că accesul la oricare dintre cele trei permisiuni de mai sus poate permite unui utilizator să își modifice propriile privilegii sau privilegiile altor persoane din sistem. Atribuie doar roluri cu aceste permisiuni utilizatorilor de încredere.', 'role_asset_desc' => 'Aceste permisiuni controlează accesul implicit la activele din sistem. Permisiunile pe Cărți, Capitole și Pagini vor suprascrie aceste permisiuni.', @@ -245,7 +246,7 @@ return [ // Webhooks 'webhooks' => 'Webhook-uri', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count declanșator eveniment:count evenimente de declanșare', 'webhooks_create' => 'Creează un nou Webhook', 'webhooks_none_created' => 'Nu au fost create webhook-uri.', 'webhooks_edit' => 'Editare Webhook', diff --git a/lang/ru/activities.php b/lang/ru/activities.php index 7a33900e4..d1a492639 100644 --- a/lang/ru/activities.php +++ b/lang/ru/activities.php @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Полка успешно удалена', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', + 'revision_restore' => 'восстановил версию', + 'revision_delete' => 'удалил версию', 'revision_delete_notification' => 'Версия успешно удалена', // Favourites 'favourite_add_notification' => '":name" добавлено в избранное', 'favourite_remove_notification' => '":name" удалено из избранного', + // Watching + 'watch_update_level_notification' => 'Настройки просмотра успешно обновлены', + // Auth - 'auth_login' => 'logged in', + 'auth_login' => 'вошёл', 'auth_register' => 'зарегистрировался как новый пользователь', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', - 'mfa_setup_method_notification' => 'Двухфакторный метод авторизации успешно настроен', - 'mfa_remove_method' => 'removed MFA method', - 'mfa_remove_method_notification' => 'Двухфакторный метод авторизации успешно удален', + 'auth_password_reset_request' => 'запросил смену пароля пользователя', + 'auth_password_reset_update' => 'сбросил пароль пользователя', + 'mfa_setup_method' => 'ностроил метод МФА', + 'mfa_setup_method_notification' => 'Многофакторный метод аутентификации успешно настроен', + 'mfa_remove_method' => 'удалил метод МФА', + 'mfa_remove_method_notification' => 'Многофакторный метод аутентификации успешно удален', // Settings - 'settings_update' => 'updated settings', + 'settings_update' => 'обновил настройки', 'settings_update_notification' => 'Настройки успешно обновлены', - 'maintenance_action_run' => 'ran maintenance action', + 'maintenance_action_run' => 'запустил техническое обслуживание', // Webhooks 'webhook_create' => 'создал вебхук', @@ -82,35 +85,40 @@ return [ 'webhook_delete_notification' => 'Вебхук успешно удален', // Users - 'user_create' => 'created user', + 'user_create' => 'создал пользователя', 'user_create_notification' => 'Пользователь успешно создан', - 'user_update' => 'updated user', + 'user_update' => 'обновил пользователя', 'user_update_notification' => 'Пользователь успешно обновлен', - 'user_delete' => 'deleted user', + 'user_delete' => 'удалил пользователя', 'user_delete_notification' => 'Пользователь успешно удален', // API Tokens - 'api_token_create' => 'created api token', + 'api_token_create' => 'создал api token', 'api_token_create_notification' => 'API токен успешно создан', - 'api_token_update' => 'updated api token', + 'api_token_update' => 'обновил api token', 'api_token_update_notification' => 'API токен успешно обновлен', - 'api_token_delete' => 'deleted api token', + 'api_token_delete' => 'удалил api token', 'api_token_delete_notification' => 'API токен успешно удален', // Roles - 'role_create' => 'created role', + 'role_create' => 'создал роль', 'role_create_notification' => 'Роль успешно создана', - 'role_update' => 'updated role', + 'role_update' => 'обновил роль', 'role_update_notification' => 'Роль успешно обновлена', - 'role_delete' => 'deleted role', + 'role_delete' => 'удалил роль', 'role_delete_notification' => 'Роль успешно удалена', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'очистил корзину', + 'recycle_bin_restore' => 'восстановлено из корзины', + 'recycle_bin_destroy' => 'удалено из корзины', + + // Comments + 'commented_on' => 'прокомментировал', + 'comment_create' => 'добавил комментарий', + 'comment_update' => 'обновил комментарий', + 'comment_delete' => 'удалил комментарий', // Other - 'commented_on' => 'прокомментировал', 'permissions_update' => 'обновил разрешения', ]; diff --git a/lang/ru/auth.php b/lang/ru/auth.php index 50ada48a2..b50d67a92 100644 --- a/lang/ru/auth.php +++ b/lang/ru/auth.php @@ -81,15 +81,15 @@ return [ 'user_invite_success_login' => 'Пароль установлен, теперь вы можете войти в систему, используя установленный пароль для доступа к :appName!', // Multi-factor Authentication - 'mfa_setup' => 'Двухфакторная аутентификация', - 'mfa_setup_desc' => 'Двухфакторная аутентификация повышает степень безопасности вашей учетной записи.', + 'mfa_setup' => 'Многофакторная аутентификация', + 'mfa_setup_desc' => 'Многофакторная аутентификация повышает степень безопасности вашей учетной записи.', 'mfa_setup_configured' => 'Настроено', 'mfa_setup_reconfigure' => 'Перенастроить', - 'mfa_setup_remove_confirmation' => 'Вы уверены, что хотите удалить этот двухфакторный метод аутентификации?', + 'mfa_setup_remove_confirmation' => 'Вы уверены, что хотите удалить этот многофакторный метод аутентификации?', 'mfa_setup_action' => 'Настройка', 'mfa_backup_codes_usage_limit_warning' => 'У вас осталось менее 5 резервных кодов, пожалуйста, создайте и сохраните новый набор перед тем, как закончатся коды, чтобы предотвратить блокировку вашей учетной записи.', 'mfa_option_totp_title' => 'Мобильное приложение', - 'mfa_option_totp_desc' => 'Для использования двухфакторной аутентификации вам понадобится мобильное приложение, поддерживающее TOTP, например Google Authenticator, Authy или Microsoft Authenticator.', + 'mfa_option_totp_desc' => 'Для использования многофакторной аутентификации вам понадобится мобильное приложение, поддерживающее TOTP, например Google Authenticator, Authy или Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Резервные коды', 'mfa_option_backup_codes_desc' => 'Безопасно хранить набор одноразовых резервных кодов, которые вы можете ввести для проверки вашей личности.', 'mfa_gen_confirm_and_enable' => 'Подтвердить и включить', @@ -98,7 +98,7 @@ return [ 'mfa_gen_backup_codes_download' => 'Скачать коды', 'mfa_gen_backup_codes_usage_warning' => 'Каждый код может быть использован только один раз', 'mfa_gen_totp_title' => 'Настройка мобильного приложения', - 'mfa_gen_totp_desc' => 'Для использования двухфакторной аутентификации вам понадобится мобильное приложение, поддерживающее TOTP, например Google Authenticator, Authy или Microsoft Authenticator.', + 'mfa_gen_totp_desc' => 'Для использования многофакторной аутентификации вам понадобится мобильное приложение, поддерживающее TOTP, например Google Authenticator, Authy или Microsoft Authenticator.', 'mfa_gen_totp_scan' => 'Отсканируйте QR-код, используя приложение для аутентификации.', 'mfa_gen_totp_verify_setup' => 'Проверить настройки', 'mfa_gen_totp_verify_setup_desc' => 'Проверьте, что все работает введя код, сгенерированный внутри вашего приложения для аутентификации, в поле ввода ниже:', @@ -106,12 +106,12 @@ return [ 'mfa_verify_access' => 'Подтвердите доступ', 'mfa_verify_access_desc' => 'Ваша учетная запись требует подтверждения личности на дополнительном уровне верификации, прежде чем вам будет предоставлен доступ. Для продолжения подтвердите вход, используя один из настроенных методов.', 'mfa_verify_no_methods' => 'Методы не настроены', - 'mfa_verify_no_methods_desc' => 'Для вашей учетной записи не найдены двухфакторные методы аутентификации. Вам нужно настроить хотя бы один метод, прежде чем получить доступ.', + 'mfa_verify_no_methods_desc' => 'Для вашей учетной записи не найдены многофакторные методы аутентификации. Вам нужно настроить хотя бы один метод, прежде чем получить доступ.', 'mfa_verify_use_totp' => 'Проверить используя мобильное приложение', 'mfa_verify_use_backup_codes' => 'Проверить используя резервный код', 'mfa_verify_backup_code' => 'Резервный код', 'mfa_verify_backup_code_desc' => 'Введите один из оставшихся резервных кодов ниже:', 'mfa_verify_backup_code_enter_here' => 'Введите резервный код', 'mfa_verify_totp_desc' => 'Введите код, сгенерированный с помощью мобильного приложения, ниже:', - 'mfa_setup_login_notification' => 'Двухфакторный метод настроен, пожалуйста, войдите снова, используя сконфигурированный метод.', + 'mfa_setup_login_notification' => 'Многофакторный метод аутентификации настроен, пожалуйста, войдите снова, используя сконфигурированный метод.', ]; diff --git a/lang/ru/common.php b/lang/ru/common.php index fa806e297..42f0b5d39 100644 --- a/lang/ru/common.php +++ b/lang/ru/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Отмена', - 'close' => 'Close', + 'close' => 'Закрыть', 'confirm' => 'Применить', 'back' => 'Назад', 'save' => 'Сохранить', @@ -42,6 +42,7 @@ return [ 'remove' => 'Удалить', 'add' => 'Добавить', 'configure' => 'Настройка', + 'manage' => 'Управлять', 'fullscreen' => 'На весь экран', 'favourite' => 'Избранное', 'unfavourite' => 'Убрать из избранного', diff --git a/lang/ru/components.php b/lang/ru/components.php index 4a9a6c51e..fdcfb80a4 100644 --- a/lang/ru/components.php +++ b/lang/ru/components.php @@ -6,8 +6,8 @@ return [ // Image Manager 'image_select' => 'Выбрать изображение', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', + 'image_list' => 'Список изображений', + 'image_details' => 'Сведения об изображении', 'image_upload' => 'Загрузить изображение', 'image_intro' => 'Здесь вы можете выбрать и управлять изображениями, которые были ранее загружены в систему.', 'image_intro_upload' => 'Загрузите новое изображение, перетянув файл в это окно, или с помощью кнопки "Загрузить изображение" выше.', @@ -17,9 +17,9 @@ return [ 'image_page_title' => 'Просмотр всех изображений, загруженных на эту страницу', 'image_search_hint' => 'Поиск по названию изображения', 'image_uploaded' => 'Загружено :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'Загружено :userName', + 'image_uploaded_to' => 'Загружено на :pageLink', + 'image_updated' => 'Обновлено :updateDate', 'image_load_more' => 'Загрузить еще', 'image_image_name' => 'Название изображения', 'image_delete_used' => 'Это изображение используется на странице ниже.', @@ -32,8 +32,8 @@ return [ 'image_upload_success' => 'Изображение успешно загружено', 'image_update_success' => 'Детали изображения успешно обновлены', 'image_delete_success' => 'Изображение успешно удалено', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', + 'image_replace' => 'Заменить изображение', + 'image_replace_success' => 'Файл изображения успешно обновлён', // Code Editor 'code_editor' => 'Изменить код', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index bca378814..621493b25 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -42,9 +42,9 @@ return [ // Permissions and restrictions 'permissions' => 'Разрешения', - 'permissions_desc' => 'Установите права доступа для переопределения прав по умолчанию, предоставленных ролями пользователей.', - 'permissions_book_cascade' => 'Разрешения, установленные для книг, автоматически распространяются на дочерние главы и страницы, если для них не определены собственные разрешения.', - 'permissions_chapter_cascade' => 'Разрешения, установленные для глав, автоматически распространяются на дочерние страницы, если для них не определены собственные разрешения.', + 'permissions_desc' => 'Установите права доступа для переопределения прав, предоставленных ролями пользователей по-умолчанию.', + 'permissions_book_cascade' => 'Права доступа, установленные для книг, автоматически распространяются на дочерние главы и страницы, если для них не определены собственные разрешения.', + 'permissions_chapter_cascade' => 'Права доступа, установленные для глав, автоматически распространяются на дочерние страницы, если для них не определены собственные разрешения.', 'permissions_save' => 'Сохранить разрешения', 'permissions_owner' => 'Владелец', 'permissions_role_everyone_else' => 'Все остальные', @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Доступы к полке обновлены', 'shelves_permissions_active' => 'Действующие разрешения полки', 'shelves_permissions_cascade_warning' => 'Разрешения на полки не наследуются автоматически содержащимся в них книгам. Это происходит потому, что книга может находиться на нескольких полках. Однако разрешения могут быть установлены для книг полки с помощью опции, приведенной ниже.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Разрешения полки на создание используется только для копирования разрешений на дочерние книги с помощью действия, описанного ниже. Они не контролируют возможность создавать книги.', 'shelves_copy_permissions_to_books' => 'Наследовать доступы книгам', 'shelves_copy_permissions' => 'Копировать доступы', 'shelves_copy_permissions_explain' => 'Это применит текущие настройки разрешений для этой полки ко всем книгам, содержащимся в ней. Перед активацией убедитесь, что все изменения разрешений этой полки были сохранены.', @@ -214,7 +214,7 @@ return [ 'pages_editing_page' => 'Редактирование страницы', 'pages_edit_draft_save_at' => 'Черновик сохранён в ', 'pages_edit_delete_draft' => 'Удалить черновик', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => 'Вы уверены, что хотите удалить черновик с изменениями? Все изменения, внесенные вами с момента последнего полного сохранения, будут утеряны и редактор будет обновлен данными с последнего сохранения страницы.', 'pages_edit_discard_draft' => 'Отменить черновик', 'pages_edit_switch_to_markdown' => 'Переключиться на Markdown', 'pages_edit_switch_to_markdown_clean' => 'Только Markdown (с возможными потерями форматирования)', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Вставить рисунок', 'pages_md_show_preview' => 'Предпросмотр', 'pages_md_sync_scroll' => 'Синхронизировать прокрутку', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Страница не находится в главе', 'pages_move' => 'Переместить страницу', 'pages_copy' => 'Скопировать страницу', @@ -255,7 +257,7 @@ return [ 'pages_revisions_created_by' => 'Создана', 'pages_revisions_date' => 'Дата версии', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Номер ревизии', + 'pages_revisions_sort_number' => 'Номер версии', 'pages_revisions_numbered' => 'Версия #:id', 'pages_revisions_numbered_changes' => 'Изменения в версии #:id', 'pages_revisions_editor' => 'Тип редактора', @@ -266,11 +268,11 @@ return [ 'pages_revisions_restore' => 'Восстановить', 'pages_revisions_none' => 'У этой страницы нет других версий', 'pages_copy_link' => 'Копировать ссылку', - 'pages_edit_content_link' => 'Jump to section in editor', + 'pages_edit_content_link' => 'Перейти к разделу в редакторе', 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', + 'pages_pointer_label' => 'Настройки раздела страницы', + 'pages_pointer_permalink' => 'Постоянная ссылка на раздел страницы', + 'pages_pointer_include_tag' => 'Раздел страницы с тегом', 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', 'pages_permissions_active' => 'Действующие разрешения на страницу', @@ -287,8 +289,8 @@ return [ 'time_b' => 'за последние :minCount минут', 'message' => ':start :time. Будьте осторожны, чтобы не перезаписывать друг друга!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'Черновик сброшен! Редактор обновлен текущим содержимым страницы', + 'pages_draft_deleted' => 'Черновик удалён! Редактор обновлен текущим содержимым страницы', 'pages_specific' => 'Конкретная страница', 'pages_is_template' => 'Шаблон страницы', @@ -366,13 +368,13 @@ return [ 'comment_new' => 'Новый комментарий', 'comment_created' => 'прокомментировал :createDiff', 'comment_updated' => 'Обновлен :updateDiff пользователем :username', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Обновлено', 'comment_deleted_success' => 'Комментарий удален', 'comment_created_success' => 'Комментарий добавлен', 'comment_updated_success' => 'Комментарий обновлен', 'comment_delete_confirm' => 'Удалить этот комментарий?', 'comment_in_reply_to' => 'В ответ на :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'Вот комментарии, которые были оставлены на этой странице. Комментарии могут быть добавлены и управляться при просмотре сохраненной страницы.', // Revision 'revision_delete_confirm' => 'Удалить эту версию?', @@ -403,4 +405,28 @@ return [ 'references' => 'Ссылки', 'references_none' => 'Нет отслеживаемых ссылок на этот элемент.', 'references_to_desc' => 'Ниже показаны все известные страницы в системе, которые ссылаются на этот элемент.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Игнорирование уведомлений', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/ru/errors.php b/lang/ru/errors.php index 6ae434840..9f17ba81c 100644 --- a/lang/ru/errors.php +++ b/lang/ru/errors.php @@ -49,7 +49,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Произошла ошибка при загрузке изображения', 'image_upload_type_error' => 'Неправильный тип загружаемого изображения', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', + 'image_upload_replace_type' => 'Замена файла изображения должна быть того же типа', 'drawing_data_not_found' => 'Данные чертежа не могут быть загружены. Возможно, файл чертежа больше не существует или у вас нет разрешения на доступ к нему.', // Attachments @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Ошибка при отправке тестового письма:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php new file mode 100644 index 000000000..5e31cc72e --- /dev/null +++ b/lang/ru/notifications.php @@ -0,0 +1,26 @@ + 'Новый комментарий на странице: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'Новая страница: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Имя страницы:', + 'detail_commenter' => 'Комментатор:', + 'detail_comment' => 'Комментарий:', + 'detail_created_by' => 'Создано:', + 'detail_updated_by' => 'Обновлено:', + + 'action_view_comment' => 'Просмотреть комментарий', + 'action_view_page' => 'Посмотреть страницу', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'ваши настройки уведомлений', +]; diff --git a/lang/ru/preferences.php b/lang/ru/preferences.php index 9b51f8e55..9cf1395ed 100644 --- a/lang/ru/preferences.php +++ b/lang/ru/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Горячие клавиши', 'shortcuts_interface' => 'Горячие клавиши интерфейса', 'shortcuts_toggle_desc' => 'Здесь вы можете включить или отключить горячие клавиши системного интерфейса, используемые для навигации и действий.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Сохранить горячие клавиши', 'shortcuts_overlay_desc' => 'Примечание: Когда горячие клавиши включены, вспомогательное наложение доступно через нажатие "?", которая будет подсвечивать доступные горячие клавиши для действий, видимых в настоящее время на экране.', 'shortcuts_update_success' => 'Настройки горячих клавиш были обновлены!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/ru/settings.php b/lang/ru/settings.php index c7fd2dc51..3555bac52 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Управление настройками приложения', 'role_export_content' => 'Экспорт контента', 'role_editor_change' => 'Изменение редактора страниц', + 'role_notifications' => 'Получение и управление уведомлениями', 'role_asset' => 'Права доступа к материалам', 'roles_system_warning' => 'Имейте в виду, что доступ к любому из указанных выше трех разрешений может позволить пользователю изменить свои собственные привилегии или привилегии других пользователей системы. Назначать роли с этими правами можно только доверенным пользователям.', 'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.', @@ -219,8 +220,8 @@ return [ 'users_api_tokens_create' => 'Создать токен', 'users_api_tokens_expires' => 'Истекает', 'users_api_tokens_docs' => 'Документация', - 'users_mfa' => 'Двухфакторная аутентификация', - 'users_mfa_desc' => 'Двухфакторная аутентификация повышает степень безопасности вашей учетной записи.', + 'users_mfa' => 'Многофакторная аутентификация', + 'users_mfa_desc' => 'Многофакторная аутентификация повышает степень безопасности вашей учетной записи.', 'users_mfa_x_methods' => 'методов настроено :count|методов сконфигурировано :count', 'users_mfa_configure' => 'Настройка методов', @@ -245,7 +246,7 @@ return [ // Webhooks 'webhooks' => 'Вебхуки', 'webhooks_index_desc' => 'Webhooks - это способ посылать данные на внешние URL-адреса при возникновении определенных действий и событий в системе, которые позволяют интегрировать события с внешними платформами, такими как системы обмена сообщениями или уведомлениями.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count событие триггера|:count событий триггера', 'webhooks_create' => 'Создать вебхук', 'webhooks_none_created' => 'Вебхуки еще не созданы.', 'webhooks_edit' => 'Редактировать вебхук', diff --git a/lang/sk/activities.php b/lang/sk/activities.php index 1c970cc97..9750afa17 100644 --- a/lang/sk/activities.php +++ b/lang/sk/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" bol pridaný medzi obľúbené', 'favourite_remove_notification' => '":name" bol odstránený z obľúbených', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'komentoval(a)', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'aktualizované oprávnenia', ]; diff --git a/lang/sk/common.php b/lang/sk/common.php index 1210af7aa..da3a44ef0 100644 --- a/lang/sk/common.php +++ b/lang/sk/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Odstrániť', 'add' => 'Pridať', 'configure' => 'Konfigurácia', + 'manage' => 'Manage', 'fullscreen' => 'Celá obrazovka', 'favourite' => 'Pridať do obľúbených', 'unfavourite' => 'Odstrániť z obľúbených', diff --git a/lang/sk/entities.php b/lang/sk/entities.php index b03b9fa83..3d4938fff 100644 --- a/lang/sk/entities.php +++ b/lang/sk/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Vložiť kresbu', 'pages_md_show_preview' => 'Zobraziť náhľad', 'pages_md_sync_scroll' => 'Posúvanie ukážky synchronizácie', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Stránka nie je v kapitole', 'pages_move' => 'Presunúť stránku', 'pages_copy' => 'Kpoírovať stránku', @@ -403,4 +405,28 @@ return [ 'references' => 'Referencie', 'references_none' => 'Neexistujú žiadne sledované referencie na túto položku.', 'references_to_desc' => 'Nižšie sú zobrazené všetky známe stránky v systéme, ktoré odkazujú na túto položku.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/sk/errors.php b/lang/sk/errors.php index 72f3154f7..7aa1769e0 100644 --- a/lang/sk/errors.php +++ b/lang/sk/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Chyba pri odosielaní testovacieho e-mailu:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/sk/notifications.php b/lang/sk/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/sk/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/sk/preferences.php b/lang/sk/preferences.php index 585207c1f..e733f0b9f 100644 --- a/lang/sk/preferences.php +++ b/lang/sk/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Skratky', 'shortcuts_interface' => 'Klávesové skratky rozhrania', 'shortcuts_toggle_desc' => 'Tu môžete povoliť alebo zakázať klávesové skratky systémového rozhrania, ktoré sa používajú na navigáciu a akcie.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Uložiť skratky', 'shortcuts_overlay_desc' => 'Poznámka: Keď sú zapnuté skratky, pomocné prekrytie je dostupné stlačením „?", ktoré zvýrazní dostupné skratky akcií,, ktoré sú momentálne viditeľné na obrazovke.', 'shortcuts_update_success' => 'Predvoľby skratiek boli aktualizované!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/sk/settings.php b/lang/sk/settings.php index a3fe1b3f9..8a903dcda 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Spravovať nastavenia aplikácie', 'role_export_content' => 'Exportovať obsah', 'role_editor_change' => 'Zmeniť editor stránky', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Oprávnenia majetku', 'roles_system_warning' => 'Uvedomte si, že prístup ku ktorémukoľvek z vyššie uvedených troch povolení môže používateľovi umožniť zmeniť svoje vlastné privilégiá alebo privilégiá ostatných v systéme. Roly s týmito povoleniami priraďujte iba dôveryhodným používateľom.', 'role_asset_desc' => 'Tieto oprávnenia regulujú prednastavený prístup k zdroju v systéme. Oprávnenia pre knihy, kapitoly a stránky majú vyššiu prioritu.', diff --git a/lang/sl/activities.php b/lang/sl/activities.php index a1d65fa84..2db012888 100644 --- a/lang/sl/activities.php +++ b/lang/sl/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" has been added to your favourites', 'favourite_remove_notification' => '":name" has been removed from your favourites', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'komentar na', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'pravice so posodobljene', ]; diff --git a/lang/sl/common.php b/lang/sl/common.php index 1b5e376e9..89e7af80c 100644 --- a/lang/sl/common.php +++ b/lang/sl/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Odstrani', 'add' => 'Dodaj', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Celozaslonski način', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/sl/entities.php b/lang/sl/entities.php index 958b65426..f4186ed9d 100644 --- a/lang/sl/entities.php +++ b/lang/sl/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Vstavi risbo', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Stran ni v poglavju', 'pages_move' => 'Premakni stran', 'pages_copy' => 'Kopiraj stran', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/sl/errors.php b/lang/sl/errors.php index b87f01034..f014b1147 100644 --- a/lang/sl/errors.php +++ b/lang/sl/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Napaka se je pojavila pri pošiljanju testne e-pošte:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/sl/notifications.php b/lang/sl/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/sl/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/sl/preferences.php b/lang/sl/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/sl/preferences.php +++ b/lang/sl/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 29114dfca..5d5bad4c9 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Nastavitve za upravljanje', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Sistemska dovoljenja', 'roles_system_warning' => 'Zavedajte se, da lahko dostop do kateregakoli od zgornjih treh dovoljenj uporabniku omogoči, da spremeni lastne privilegije ali privilegije drugih v sistemu. Vloge s temi dovoljenji dodelite samo zaupanja vrednim uporabnikom.', 'role_asset_desc' => 'Ta dovoljenja nadzorujejo privzeti dostop do sredstev v sistemu. Dovoljenja za knjige, poglavja in strani bodo razveljavila ta dovoljenja.', diff --git a/lang/sv/activities.php b/lang/sv/activities.php index 5285a9b75..46a2f4f3c 100644 --- a/lang/sv/activities.php +++ b/lang/sv/activities.php @@ -58,9 +58,12 @@ return [ 'favourite_add_notification' => '":name" har lagts till i dina favoriter', 'favourite_remove_notification' => '":name" har tagits bort från dina favoriter', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', + 'auth_register' => 'registrerad som ny användare', 'auth_password_reset_request' => 'requested user password reset', 'auth_password_reset_update' => 'reset user password', 'mfa_setup_method' => 'configured MFA method', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'kommenterade', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'uppdaterade behörigheter', ]; diff --git a/lang/sv/auth.php b/lang/sv/auth.php index b480fd03d..e77f4c669 100644 --- a/lang/sv/auth.php +++ b/lang/sv/auth.php @@ -26,7 +26,7 @@ return [ 'remember_me' => 'Kom ihåg mig', 'ldap_email_hint' => 'Vänligen ange en e-postadress att använda till kontot.', 'create_account' => 'Skapa konto', - 'already_have_account' => 'Har du redan en användare?', + 'already_have_account' => 'Har du redan ett konto?', 'dont_have_account' => 'Har du ingen användare?', 'social_login' => 'Logga in genom socialt medie', 'social_registration' => 'Registrera dig genom socialt media', diff --git a/lang/sv/common.php b/lang/sv/common.php index c9a925ef7..35ba4326a 100644 --- a/lang/sv/common.php +++ b/lang/sv/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Avbryt', - 'close' => 'Close', + 'close' => 'Stäng', 'confirm' => 'Bekräfta', 'back' => 'Bakåt', 'save' => 'Spara', @@ -42,6 +42,7 @@ return [ 'remove' => 'Radera', 'add' => 'Lägg till', 'configure' => 'Konfigurera', + 'manage' => 'Hantera', 'fullscreen' => 'Helskärm', 'favourite' => 'Favorit', 'unfavourite' => 'Ta bort favorit', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index c2f968adf..c2a419749 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Infoga teckning', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Sidan ligger inte i något kapitel', 'pages_move' => 'Flytta sida', 'pages_copy' => 'Kopiera sida', @@ -403,4 +405,28 @@ return [ 'references' => 'Referenser', 'references_none' => 'Det finns inga referenser kopplade till detta objekt.', 'references_to_desc' => 'Nedan visas alla kända sidor i systemet som länkar till detta objekt.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignorera', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/sv/errors.php b/lang/sv/errors.php index f33d57538..e57928578 100644 --- a/lang/sv/errors.php +++ b/lang/sv/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Ett fel uppstod när ett test mail skulle skickas:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/sv/notifications.php b/lang/sv/notifications.php new file mode 100644 index 000000000..b7702558c --- /dev/null +++ b/lang/sv/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'Visa kommentar', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/sv/preferences.php b/lang/sv/preferences.php index e9a47461b..766e6188f 100644 --- a/lang/sv/preferences.php +++ b/lang/sv/preferences.php @@ -5,7 +5,9 @@ */ return [ - 'shortcuts' => 'Shortcuts', + 'preferences' => 'Preferences', + + 'shortcuts' => 'Genvägar', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/sv/settings.php b/lang/sv/settings.php index afbd5f58e..db58eb38a 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Hantera appinställningar', 'role_export_content' => 'Exportera innehåll', 'role_editor_change' => 'Ändra sidredigerare', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Tillgång till innehåll', 'roles_system_warning' => 'Var medveten om att åtkomst till någon av ovanstående tre behörigheter kan tillåta en användare att ändra sina egna rättigheter eller andras rättigheter i systemet. Tilldela endast roller med dessa behörigheter till betrodda användare.', 'role_asset_desc' => 'Det här är standardinställningarna för allt innehåll i systemet. Eventuella anpassade rättigheter på böcker, kapitel och sidor skriver över dessa inställningar.', diff --git a/lang/tr/activities.php b/lang/tr/activities.php index 0eff1dea6..be6a0777e 100644 --- a/lang/tr/activities.php +++ b/lang/tr/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" favorilerinize eklendi', 'favourite_remove_notification' => '":name" favorilerinizden çıkarıldı', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'oturum açıldı', 'auth_register' => 'yeni kullanıcı olarak kayıt yapıldı', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'yorum yaptı', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'güncellenmiş izinler', ]; diff --git a/lang/tr/common.php b/lang/tr/common.php index c08c4a193..fe2e6315a 100644 --- a/lang/tr/common.php +++ b/lang/tr/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Kaldır', 'add' => 'Ekle', 'configure' => 'Yapılandır', + 'manage' => 'Manage', 'fullscreen' => 'Tam Ekran', 'favourite' => 'Favoriye ekle', 'unfavourite' => 'Favorilerden çıkar', diff --git a/lang/tr/entities.php b/lang/tr/entities.php index 2288f882d..27bf1a33c 100644 --- a/lang/tr/entities.php +++ b/lang/tr/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Çizim Ekle', 'pages_md_show_preview' => 'Önizlemeyi göster', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Bu sayfa, bir bölüme ait değil', 'pages_move' => 'Sayfayı Taşı', 'pages_copy' => 'Sayfayı Kopyala', @@ -403,4 +405,28 @@ return [ 'references' => 'Referanslar', 'references_none' => 'Bu öğeye ilişkin takip edilen bir referans bulunmamaktadır.', 'references_to_desc' => 'Aşağıda, sistemde bu öğeye bağlantı veren bilinen tüm sayfalar gösterilmektedir.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/tr/errors.php b/lang/tr/errors.php index 455fe8039..c768d46dc 100644 --- a/lang/tr/errors.php +++ b/lang/tr/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Test e-postası gönderilirken bir hata meydana geldi:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/tr/notifications.php b/lang/tr/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/tr/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/tr/preferences.php b/lang/tr/preferences.php index fef326ff3..0a75db214 100644 --- a/lang/tr/preferences.php +++ b/lang/tr/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Kısayollar', 'shortcuts_interface' => 'Klavye Kısayollarını Görüntüle', 'shortcuts_toggle_desc' => 'Burada, gezinme ve eylemler için kullanılan klavye sistem arayüzü kısayollarını etkinleştirebilir veya devre dışı bırakabilirsiniz.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Kısayolları Kaydet', 'shortcuts_overlay_desc' => 'Not: Kısayollar etkinleştirildiğinde, "?" tuşuna basılarak o anda ekranda görünen eylemler için mevcut kısayolları vurgulayan bir yardımcı yer paylaşımı kullanılabilir.', 'shortcuts_update_success' => 'Kısayol tercihleri güncellendi!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/tr/settings.php b/lang/tr/settings.php index af7130887..ddc32dfb1 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Uygulama ayarlarını yönet', 'role_export_content' => 'İçeriği dışa aktar', 'role_editor_change' => 'Yazı editörünü değiştir', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Varlık Yetkileri', 'roles_system_warning' => 'Yukarıdaki üç izinden herhangi birine erişimin, kullanıcının kendi ayrıcalıklarını veya sistemdeki diğerlerinin ayrıcalıklarını değiştirmesine izin verebileceğini unutmayın. Yalnızca bu izinlere sahip rolleri güvenilir kullanıcılara atayın.', 'role_asset_desc' => 'Bu izinler, sistem içindeki varlıklara varsayılan erişim izinlerini ayarlar. Kitaplar, bölümler ve sayfalar üzerindeki izinler, buradaki izinleri geçersiz kılar.', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index 0a6c950e7..243fafa64 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":ім\'я" було додане до ваших улюлених', 'favourite_remove_notification' => '":ім\'я" було видалено з ваших улюблених', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'прокоментував', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'видалений коментар', + + // Other 'permissions_update' => 'оновив дозволи', ]; diff --git a/lang/uk/common.php b/lang/uk/common.php index f1811e612..a4b37bb83 100644 --- a/lang/uk/common.php +++ b/lang/uk/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Видалити', 'add' => 'Додати', 'configure' => 'Налаштувати', + 'manage' => 'Manage', 'fullscreen' => 'На весь екран', 'favourite' => 'Улюблене', 'unfavourite' => 'Прибрати з обраного', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index 63889b26a..b95fcc115 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Вставити малюнок', 'pages_md_show_preview' => 'Показати попередній перегляд', 'pages_md_sync_scroll' => 'Синхронізація прокручування попереднього перегляду', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Сторінка не знаходиться в розділі', 'pages_move' => 'Перемістити сторінку', 'pages_copy' => 'Копіювати сторінку', @@ -403,4 +405,28 @@ return [ 'references' => 'Посилання', 'references_none' => 'Немає відслідковуваних посилань для цього елемента.', 'references_to_desc' => 'Показані нижче всі відомі сторінки в системі, що посилаються на цей елемент.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/uk/errors.php b/lang/uk/errors.php index 3bffb10f3..04f41ee70 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Помилка під час надсилання тестового електронного листа:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/uk/notifications.php b/lang/uk/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/uk/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/uk/preferences.php b/lang/uk/preferences.php index 55934b4fe..9e44a3c82 100644 --- a/lang/uk/preferences.php +++ b/lang/uk/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Ярлики', 'shortcuts_interface' => 'Комбінації клавіш інтерфейсу', 'shortcuts_toggle_desc' => 'Тут ви можете увімкнути або вимкнути ярлики інтерфейсу клавіатури, які використовуються для навігації та дій.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Зберегти ярлики', 'shortcuts_overlay_desc' => 'Примітка: якщо ярлики ввімкнено, допоміжне накладання доступне, натиснувши "?" який виділить доступні ярлики для дій, які зараз видно на екрані.', 'shortcuts_update_success' => 'Налаштування ярликів оновлено!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 45b1cfa86..6b491a70a 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Керування налаштуваннями програми', 'role_export_content' => 'Вміст експорту', 'role_editor_change' => 'Змінити редактор сторінок', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Дозволи', 'roles_system_warning' => 'Майте на увазі, що доступ до будь-якого з вищезазначених трьох дозволів може дозволити користувачеві змінювати власні привілеї або привілеї інших в системі. Ролі з цими дозволами призначайте лише довіреним користувачам.', 'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.', diff --git a/lang/uz/activities.php b/lang/uz/activities.php index eae266eff..6d2ec3408 100644 --- a/lang/uz/activities.php +++ b/lang/uz/activities.php @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" sevimlilaringizga qo\'shildi', 'favourite_remove_notification' => '":name" sevimlilaringizdan olib tashlandi', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => 'fikr qoldirdi', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => 'yangilangan huquqlar', ]; diff --git a/lang/uz/common.php b/lang/uz/common.php index de7937b2b..47b74d5b6 100644 --- a/lang/uz/common.php +++ b/lang/uz/common.php @@ -42,6 +42,7 @@ return [ 'remove' => 'Remove', 'add' => 'Add', 'configure' => 'Configure', + 'manage' => 'Manage', 'fullscreen' => 'Fullscreen', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', diff --git a/lang/uz/entities.php b/lang/uz/entities.php index f0125cf74..672f9a991 100644 --- a/lang/uz/entities.php +++ b/lang/uz/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Insert Drawing', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Page is not in a chapter', 'pages_move' => 'Move Page', 'pages_copy' => 'Copy Page', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/uz/errors.php b/lang/uz/errors.php index 23c326f9e..4cde4cea3 100644 --- a/lang/uz/errors.php +++ b/lang/uz/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/uz/notifications.php b/lang/uz/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/uz/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/uz/preferences.php b/lang/uz/preferences.php index e9a47461b..118e8ba82 100644 --- a/lang/uz/preferences.php +++ b/lang/uz/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Shortcuts', 'shortcuts_interface' => 'Interface Keyboard Shortcuts', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 49a726bcb..3b702d29a 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Manage app settings', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', diff --git a/lang/vi/activities.php b/lang/vi/activities.php index 9a5ac7540..17259f1e2 100644 --- a/lang/vi/activities.php +++ b/lang/vi/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'đã khôi phục trang', 'page_restore_notification' => 'Trang đã được khôi phục thành công', 'page_move' => 'đã di chuyển trang', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Đã di chuyển trang thành công', // Chapters 'chapter_create' => 'đã tạo chương', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'đã xóa chương', 'chapter_delete_notification' => 'Chương đã được xóa thành công', 'chapter_move' => 'đã di chuyển chương', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Đã chuyển chương thành công', // Books 'book_create' => 'đã tạo sách', @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Xoá giá sách thành công', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'đã khôi phục sửa đổi', + 'revision_delete' => 'đã xóa bản sửa đổi', + 'revision_delete_notification' => 'Bản sửa đổi đã được xóa thành công', // Favourites 'favourite_add_notification' => '":name" đã được thêm vào danh sách yêu thích của bạn', 'favourite_remove_notification' => '":name" đã được gỡ khỏi danh sách yêu thích của bạn', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'đăng nhập', + 'auth_register' => 'đã đăng ký như người dùng mới', + 'auth_password_reset_request' => 'yêu cầu người dùng đặt lại mật khẩu', + 'auth_password_reset_update' => 'đặt lại mật khẩu người dùng', + 'mfa_setup_method' => 'đã định cấu hình phương thức MFA', 'mfa_setup_method_notification' => 'Cấu hình xác thực nhiều bước thành công', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'loại bỏ phương thức MFA', 'mfa_remove_method_notification' => 'Đã gỡ xác thực nhiều bước', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'cập nhật cài đặt', + 'settings_update_notification' => 'Cài đặt đã cập nhật thành công', + 'maintenance_action_run' => 'chạy hành động bảo trì', // Webhooks 'webhook_create' => 'đã tạo webhook', @@ -82,11 +85,11 @@ return [ 'webhook_delete_notification' => 'Webhook đã được xóa thành công', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'người dùng đã tạo', + 'user_create_notification' => 'Người dùng được tạo thành công', + 'user_update' => 'người dùng được cập nhật', 'user_update_notification' => 'Người dùng được cập nhật thành công', - 'user_delete' => 'deleted user', + 'user_delete' => 'người dùng đã bị xóa', 'user_delete_notification' => 'Người dùng đã được xóa thành công', // API Tokens @@ -102,15 +105,20 @@ return [ 'role_create_notification' => 'Vai trò mới đã được tạo thành công', 'role_update' => 'updated role', 'role_update_notification' => 'Vai trò đã được cập nhật thành công', - 'role_delete' => 'deleted role', + 'role_delete' => 'đã xóa vai trò', 'role_delete_notification' => 'Vai trò đã được xóa thành công', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'làm trống thùng rác', + 'recycle_bin_restore' => 'khôi phục từ thùng rác', + 'recycle_bin_destroy' => 'đã xóa khỏi thùng rác', + + // Comments + 'commented_on' => 'đã bình luận về', + 'comment_create' => 'thêm bình luận', + 'comment_update' => 'cập nhật bình luận', + 'comment_delete' => 'đã xóa bình luận', // Other - 'commented_on' => 'đã bình luận về', 'permissions_update' => 'các quyền đã được cập nhật', ]; diff --git a/lang/vi/common.php b/lang/vi/common.php index 58cee1477..95cc7e13c 100644 --- a/lang/vi/common.php +++ b/lang/vi/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Huỷ', - 'close' => 'Close', + 'close' => 'Đóng', 'confirm' => 'Xác nhận', 'back' => 'Quay lại', 'save' => 'Lưu', @@ -42,6 +42,7 @@ return [ 'remove' => 'Xóa bỏ', 'add' => 'Thêm', 'configure' => 'Cấu hình', + 'manage' => 'Manage', 'fullscreen' => 'Toàn màn hình', 'favourite' => 'Yêu thích', 'unfavourite' => 'Bỏ yêu thích', diff --git a/lang/vi/components.php b/lang/vi/components.php index 2f1de1c93..3ce775e23 100644 --- a/lang/vi/components.php +++ b/lang/vi/components.php @@ -6,8 +6,8 @@ return [ // Image Manager 'image_select' => 'Chọn Ảnh', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', + 'image_list' => 'Danh sách hình ảnh', + 'image_details' => 'Chi tiết hình ảnh', 'image_upload' => 'Tải ảnh lên', 'image_intro' => 'Bạn có thể lựa chọn và quản lý các hình ảnh đã được tải lên hệ thống từ trước ở đây.', 'image_intro_upload' => 'Tải lên ảnh mới bằng cách kéo và thả nó vào cửa sổ này, hoặc sử dụng nút tải ảnh ở bên trên.', @@ -17,9 +17,9 @@ return [ 'image_page_title' => 'Xem các ảnh đã được tải lên trong trang này', 'image_search_hint' => 'Tìm kiếm ảnh bằng tên', 'image_uploaded' => 'Đã tải lên :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'Tải lên bởi :userName', + 'image_uploaded_to' => 'Đã tải lên :pageLink', + 'image_updated' => 'Đã cập nhật :updateDate', 'image_load_more' => 'Hiện thêm', 'image_image_name' => 'Tên Ảnh', 'image_delete_used' => 'Ảnh này được sử dụng trong các trang dưới đây.', @@ -32,8 +32,8 @@ return [ 'image_upload_success' => 'Ảnh đã tải lên thành công', 'image_update_success' => 'Chi tiết ảnh được cập nhật thành công', 'image_delete_success' => 'Ảnh đã được xóa thành công', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', + 'image_replace' => 'Thay thế hình ảnh', + 'image_replace_success' => 'Đã cập nhật thành công tệp hình ảnh', // Code Editor 'code_editor' => 'Sửa Mã', diff --git a/lang/vi/entities.php b/lang/vi/entities.php index 542a819d9..824eaa201 100644 --- a/lang/vi/entities.php +++ b/lang/vi/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Được cập nhật :timeLength', 'meta_updated_name' => 'Được cập nhật :timeLength bởi :user', 'meta_owned_name' => 'Được sở hữu bởi :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_page_count' => 'Được tham chiếu trên :count page|Được tham chiếu trên :count pages', 'entity_select' => 'Chọn thực thể', 'entity_select_lack_permission' => 'Bạn không có quyền để chọn mục này', 'images' => 'Ảnh', @@ -49,8 +49,8 @@ return [ 'permissions_owner' => 'Chủ sở hữu', 'permissions_role_everyone_else' => 'Những người khác', 'permissions_role_everyone_else_desc' => 'Đặt quyền cho tất cả vai trò không được ghi đè cụ thể.', - 'permissions_role_override' => 'Override permissions for role', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions_role_override' => 'Ghi đè quyền cho vai trò', + 'permissions_inherit_defaults' => 'Kế thừa giá trị mặc định', // Search 'search_results' => 'Kết quả Tìm kiếm', @@ -93,14 +93,14 @@ return [ 'shelves_save' => 'Lưu Giá', 'shelves_books' => 'Sách trên Giá này', 'shelves_add_books' => 'Thêm sách vào Giá này', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', + 'shelves_drag_books' => 'Kéo sách bên dưới để thêm vào kệ sách này', 'shelves_empty_contents' => 'Giá này không có sách nào', 'shelves_edit_and_assign' => 'Chỉnh sửa kệ để gán sách', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", + 'shelves_edit_named' => 'Chỉnh sửa kệ :name', + 'shelves_edit' => 'Chỉnh sửa kệ', + 'shelves_delete' => 'Xóa kệ', + 'shelves_delete_named' => 'Xóa kệ :name', + 'shelves_delete_explain' => "Thao tác này sẽ xóa kệ có tên ':name'. Sách chứa sẽ không bị xóa.", 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', 'shelves_permissions' => 'Shelf Permissions', 'shelves_permissions_updated' => 'Shelf Permissions Updated', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => 'Chèn bản vẽ', 'pages_md_show_preview' => 'Show preview', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => 'Trang không nằm trong một chương', 'pages_move' => 'Di chuyển Trang', 'pages_copy' => 'Sao chép Trang', @@ -268,11 +270,11 @@ return [ 'pages_copy_link' => 'Sao chép Liên kết', 'pages_edit_content_link' => 'Jump to section in editor', 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_pointer_label' => 'Tùy chọn phần trang', + 'pages_pointer_permalink' => 'Phần trang Liên kết cố định', + 'pages_pointer_include_tag' => 'Phần trang bao gồm thẻ', + 'pages_pointer_toggle_link' => 'Chế độ Liên kết cố định, Nhấn để hiển thị thẻ bao gồm', + 'pages_pointer_toggle_include' => 'Bao gồm chế độ thẻ, Nhấn để hiển thị liên kết cố định', 'pages_permissions_active' => 'Đang bật các quyền hạn từ Trang', 'pages_initial_revision' => 'Đăng bài mở đầu', 'pages_references_update_revision' => 'System auto-update of internal links', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/vi/errors.php b/lang/vi/errors.php index c53fe1c23..0fcf022d3 100644 --- a/lang/vi/errors.php +++ b/lang/vi/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => 'Lỗi khi gửi email thử:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/vi/notifications.php b/lang/vi/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/vi/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/vi/preferences.php b/lang/vi/preferences.php index f6dd5a38a..c96c2b0df 100644 --- a/lang/vi/preferences.php +++ b/lang/vi/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => 'Lối tắt', 'shortcuts_interface' => 'Giao diện phím tắt', 'shortcuts_toggle_desc' => 'Tại đây, bạn có thể bật hoặc tắt các phím tắt trên giao diện hệ thống bàn phím, được sử dụng để điều hướng và thực hiện các thao tác.', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => 'Lưu phím tắt', 'shortcuts_overlay_desc' => 'Lưu ý: Khi các phím tắt được bật, lớp phủ trợ giúp sẽ khả dụng bằng cách nhấn "?" sẽ làm nổi bật các lối tắt khả dụng cho các tác vụ hiện đang hiển thị trên màn hình.', 'shortcuts_update_success' => 'Các tùy chọn phím tắt đã được cập nhật!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/vi/settings.php b/lang/vi/settings.php index 3ece26148..677cc2bad 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => 'Quản lý cài đặt của ứng dụng', 'role_export_content' => 'Export content', 'role_editor_change' => 'Change page editor', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => 'Quyền tài sản (asset)', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'Các quyền này điều khiển truy cập mặc định tới tài sản (asset) nằm trong hệ thống. Quyền tại Sách, Chường và Trang se ghi đè các quyền này.', diff --git a/lang/zh_CN/activities.php b/lang/zh_CN/activities.php index 4a83b9b83..1d7272eec 100644 --- a/lang/zh_CN/activities.php +++ b/lang/zh_CN/activities.php @@ -50,28 +50,31 @@ return [ 'bookshelf_delete_notification' => '书架删除成功', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => '已还原修改', + 'revision_delete' => '已删除修订', + 'revision_delete_notification' => '修订删除成功', // Favourites 'favourite_add_notification' => '":name" 已添加到您的收藏', 'favourite_remove_notification' => '":name" 已从您的收藏中删除', + // Watching + 'watch_update_level_notification' => '关注偏好设置已更新成功', + // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => '已登录', + 'auth_register' => '已注册为新用户', + 'auth_password_reset_request' => '已请求密码重置', + 'auth_password_reset_update' => '重置用户密码', + 'mfa_setup_method' => '已配置多重身份验证', 'mfa_setup_method_notification' => '多重身份认证设置成功', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => '已移除多重身份验证', 'mfa_remove_method_notification' => '多重身份认证已成功移除', // Settings - 'settings_update' => 'updated settings', + 'settings_update' => '设置已更新', 'settings_update_notification' => '设置更新成功', - 'maintenance_action_run' => 'ran maintenance action', + 'maintenance_action_run' => '维护操作已运行', // Webhooks 'webhook_create' => 'Webhook 已创建', @@ -82,35 +85,40 @@ return [ 'webhook_delete_notification' => 'Webhook 删除成功', // Users - 'user_create' => 'created user', + 'user_create' => '用户已创建', 'user_create_notification' => '用户创建成功', - 'user_update' => 'updated user', + 'user_update' => '用户已更新', 'user_update_notification' => '用户更新成功', - 'user_delete' => 'deleted user', + 'user_delete' => '用户已删除', 'user_delete_notification' => '成功移除用户', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'API 令牌已创建', + 'api_token_create_notification' => '成功创建 API 令牌', + 'api_token_update' => 'API 令牌已更新', + 'api_token_update_notification' => 'API 令牌更新成功', + 'api_token_delete' => '已删除 API 令牌', + 'api_token_delete_notification' => 'API 令牌删除成功', // Roles - 'role_create' => 'created role', + 'role_create' => '角色已创建', 'role_create_notification' => '角色创建成功', - 'role_update' => 'updated role', + 'role_update' => '角色已更新', 'role_update_notification' => '角色更新成功', - 'role_delete' => 'deleted role', + 'role_delete' => '角色已删除', 'role_delete_notification' => '角色删除成功', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => '回收站已清空', + 'recycle_bin_restore' => '已从回收站中恢复', + 'recycle_bin_destroy' => '已从回收站中移除', + + // Comments + 'commented_on' => '评论', + 'comment_create' => '评论已添加', + 'comment_update' => '评论已更新', + 'comment_delete' => '评论已删除', // Other - 'commented_on' => '评论', 'permissions_update' => '权限已更新', ]; diff --git a/lang/zh_CN/common.php b/lang/zh_CN/common.php index 3d7e9682f..c2103d0e7 100644 --- a/lang/zh_CN/common.php +++ b/lang/zh_CN/common.php @@ -42,6 +42,7 @@ return [ 'remove' => '删除', 'add' => '添加', 'configure' => '配置', + 'manage' => '管理', 'fullscreen' => '全屏', 'favourite' => '收藏', 'unfavourite' => '取消收藏', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index d06ebccdf..31cd1bc6e 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => '书架权限已更新', 'shelves_permissions_active' => '书架权限已启用', 'shelves_permissions_cascade_warning' => '书架上的权限不会自动应用到书架里的图书上,这是因为图书可以在多个书架上存在。使用下面的选项可以将权限复制到书架里的图书上。', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => '书架创建权限仅用于使用下面的操作将权限复制到子图书。这个权限不是用来控制创建书籍的。', 'shelves_copy_permissions_to_books' => '将权限复制到图书', 'shelves_copy_permissions' => '复制权限', 'shelves_copy_permissions_explain' => '此操作会将此书架的当前权限设置应用于其中包含的所有图书上。 启用前请确保已保存对此书架权限的任何更改。', @@ -214,7 +214,7 @@ return [ 'pages_editing_page' => '正在编辑页面', 'pages_edit_draft_save_at' => '草稿保存于 ', 'pages_edit_delete_draft' => '删除草稿', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => '您确定要删除您的草稿页面更改吗?自上次完整保存以来的所有更改都将丢失,编辑器将更新为最新非草稿页面。', 'pages_edit_discard_draft' => '放弃草稿', 'pages_edit_switch_to_markdown' => '切换到 Markdown 编辑器', 'pages_edit_switch_to_markdown_clean' => '(整理内容)', @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => '插入图表', 'pages_md_show_preview' => '显示预览', 'pages_md_sync_scroll' => '同步预览滚动', + 'pages_drawing_unsaved' => '找到未保存的绘图', + 'pages_drawing_unsaved_confirm' => '从之前保存失败的绘图中发现了可恢复的数据。您想恢复并继续编辑这个未保存的绘图吗?', 'pages_not_in_chapter' => '本页面不在某章节中', 'pages_move' => '移动页面', 'pages_copy' => '复制页面', @@ -267,12 +269,12 @@ return [ 'pages_revisions_none' => '此页面没有修订', 'pages_copy_link' => '复制链接', 'pages_edit_content_link' => '跳转到编辑器中的部分', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_pointer_enter_mode' => '进入部分选择模式', + 'pages_pointer_label' => '页面部分选项', + 'pages_pointer_permalink' => '页面部分永久链接', + 'pages_pointer_include_tag' => '页面部分包含标签', + 'pages_pointer_toggle_link' => '永久链接模式,按下显示包含标签', + 'pages_pointer_toggle_include' => '包含标签模式,按下显示永久链接', 'pages_permissions_active' => '页面权限已启用', 'pages_initial_revision' => '初始发布', 'pages_references_update_revision' => '系统自动更新的内部链接', @@ -287,8 +289,8 @@ return [ 'time_b' => '在最近 :minCount 分钟', 'message' => ':time :start。注意不要覆盖他人的更新!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => '草稿已丢弃!编辑器已更新到当前页面内容', + 'pages_draft_deleted' => '草稿已删除!编辑器已更新为当前页面内容', 'pages_specific' => '具体页面', 'pages_is_template' => '页面模板', @@ -372,7 +374,7 @@ return [ 'comment_updated_success' => '评论已更新', 'comment_delete_confirm' => '您确定要删除这条评论?', 'comment_in_reply_to' => '回复 :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => '这里是此页面上留下的评论。查看已保存的页面时可以添加和管理评论。', // Revision 'revision_delete_confirm' => '您确定要删除此修订版吗?', @@ -403,4 +405,28 @@ return [ 'references' => '引用', 'references_none' => '没有跟踪到对此项目的引用。', 'references_to_desc' => '下面显示的是系统中所有已知链接到这个项目的页面。', + + // Watch Options + 'watch' => '关注', + 'watch_title_default' => '默认偏好设置', + 'watch_desc_default' => '将关注设置恢复为默认通知偏好设置。', + 'watch_title_ignore' => '忽略', + 'watch_desc_ignore' => '忽略所有通知,包括来自用户级偏好的通知。', + 'watch_title_new' => '新页面', + 'watch_desc_new' => '在此项目中创建任何新页面时通知我。', + 'watch_title_updates' => '所有页面更新', + 'watch_desc_updates' => '在所有新页面和页面更改时通知我。', + 'watch_desc_updates_page' => '在有页面发生更改时通知我。', + 'watch_title_comments' => '所有页面更新和评论', + 'watch_desc_comments' => '在有新页面、页面更改和新评论时通知我。', + 'watch_desc_comments_page' => '在有页面更改和新评论时通知我。', + 'watch_change_default' => '更改默认通知偏好', + 'watch_detail_ignore' => '忽略通知', + 'watch_detail_new' => '已关注新页面', + 'watch_detail_updates' => '已关注新页面和更新', + 'watch_detail_comments' => '已关注新页面、更新和评论', + 'watch_detail_parent_book' => '已关注—继承自父图书', + 'watch_detail_parent_book_ignore' => '已忽略—继承自父图书', + 'watch_detail_parent_chapter' => '已关注—继承自父章节', + 'watch_detail_parent_chapter_ignore' => '已忽略—继承自父章节', ]; diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index ce99c6722..f3045fb97 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => '发送测试电子邮件时出现错误:', + // HTTP errors + 'http_ssr_url_no_match' => 'URL 与已配置的 SSR 主机不匹配', ]; diff --git a/lang/zh_CN/notifications.php b/lang/zh_CN/notifications.php new file mode 100644 index 000000000..363ba134b --- /dev/null +++ b/lang/zh_CN/notifications.php @@ -0,0 +1,26 @@ + '页面上有新评论::pageName', + 'new_comment_intro' => '一位用户在 :appName: 的页面上发表了评论', + 'new_page_subject' => '新页面::pageName', + 'new_page_intro' => ':appName: 中创建了一个新页面', + 'updated_page_subject' => '页面更新::pageName', + 'updated_page_intro' => ':appName: 中的一个页面已被更新', + 'updated_page_debounce' => '为了防止出现大量通知,一段时间内您不会收到同一编辑者再次编辑本页面的通知。', + + 'detail_page_name' => '页面名称:', + 'detail_commenter' => '评论者:', + 'detail_comment' => '评论:', + 'detail_created_by' => '创建者:', + 'detail_updated_by' => '更新者:', + + 'action_view_comment' => '查看评论', + 'action_view_page' => '查看页面', + + 'footer_reason' => '向您发送此通知是因为 :link 涵盖了该项目的此类活动。', + 'footer_reason_link' => '个人通知偏好设置', +]; diff --git a/lang/zh_CN/preferences.php b/lang/zh_CN/preferences.php index 0e1f378aa..36408ca1f 100644 --- a/lang/zh_CN/preferences.php +++ b/lang/zh_CN/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => '偏好设置', + 'shortcuts' => '快捷键', 'shortcuts_interface' => '界面键盘快捷键', 'shortcuts_toggle_desc' => '你可以启用或禁用键盘系统界面快捷键,这些快捷键用于导航和操作。', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => '保存快捷键', 'shortcuts_overlay_desc' => '注意:当快捷键启用时,可以按"?"键来打开帮助,它将突出显示当前屏幕上可见操作的快捷键。', 'shortcuts_update_success' => '快捷键设置已更新!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => '管理可用于导航系统用户界面的快捷键。', + + 'notifications' => '通知偏好', + 'notifications_desc' => '控制在系统内发生某些活动时您会收到的电子邮件通知。', + 'notifications_opt_own_page_changes' => '在我拥有的页面被修改时通知我', + 'notifications_opt_own_page_comments' => '在我拥有的页面上有新评论时通知我', + 'notifications_opt_comment_replies' => '在有人回复我的频率时通知我', + 'notifications_save' => '保存偏好设置', + 'notifications_update_success' => '通知偏好设置已更新!', + 'notifications_watched' => '已关注和忽略的项目', + 'notifications_watched_desc' => ' 下面是已应用自定义关注选项的项目。要更新您的关注设置,请查看该项目,然后在该项目的侧边栏中找到关注选项。', + + 'profile_overview_desc' => ' 管理用户个人资料信息,包括首选语言和验证选项。', +]; diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index fe389e3fb..0dcc6d5b5 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => '管理 App 设置', 'role_export_content' => '导出内容', 'role_editor_change' => '更改页面编辑器', + 'role_notifications' => '管理和接收通知', 'role_asset' => '资源许可', 'roles_system_warning' => '请注意,拥有以上三个权限中的任何一个都会允许用户更改自己的权限或系统中其他人的权限。 请只将拥有这些权限的角色分配给你信任的用户。', 'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍、章节和页面上的权限将覆盖这里的权限设定。', diff --git a/lang/zh_TW/activities.php b/lang/zh_TW/activities.php index ca22e6066..be5136a0e 100644 --- a/lang/zh_TW/activities.php +++ b/lang/zh_TW/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => '已還原頁面', 'page_restore_notification' => '頁面已還原成功', 'page_move' => '已移動頁面', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => '頁面已成功移動', // Chapters 'chapter_create' => '已建立章節', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => '已刪除章節', 'chapter_delete_notification' => '章節已刪除成功', 'chapter_move' => '已移動章節', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => '章節已移動成功', // Books 'book_create' => '已建立書本', @@ -58,6 +58,9 @@ return [ 'favourite_add_notification' => '":name" 已加入到你的最愛', 'favourite_remove_notification' => '":name" 已從你的最愛移除', + // Watching + 'watch_update_level_notification' => 'Watch preferences successfully updated', + // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', @@ -110,7 +113,12 @@ return [ 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', - // Other + // Comments 'commented_on' => '評論', + 'comment_create' => 'added comment', + 'comment_update' => 'updated comment', + 'comment_delete' => 'deleted comment', + + // Other 'permissions_update' => '更新權限', ]; diff --git a/lang/zh_TW/common.php b/lang/zh_TW/common.php index 0da1b8c13..67ebe0444 100644 --- a/lang/zh_TW/common.php +++ b/lang/zh_TW/common.php @@ -42,6 +42,7 @@ return [ 'remove' => '移除', 'add' => '新增', 'configure' => '配置', + 'manage' => 'Manage', 'fullscreen' => '全螢幕', 'favourite' => '最愛', 'unfavourite' => '取消最愛', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index 10f927919..2e6839cd4 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -239,6 +239,8 @@ return [ 'pages_md_insert_drawing' => '插入繪圖', 'pages_md_show_preview' => '顯示預覽', 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_drawing_unsaved' => 'Unsaved Drawing Found', + 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', 'pages_not_in_chapter' => '頁面不在章節中', 'pages_move' => '移動頁面', 'pages_copy' => '複製頁面', @@ -403,4 +405,28 @@ return [ 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + + // Watch Options + 'watch' => 'Watch', + 'watch_title_default' => 'Default Preferences', + 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_title_ignore' => 'Ignore', + 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', + 'watch_title_new' => 'New Pages', + 'watch_desc_new' => 'Notify when any new page is created within this item.', + 'watch_title_updates' => 'All Page Updates', + 'watch_desc_updates' => 'Notify upon all new pages and page changes.', + 'watch_desc_updates_page' => 'Notify upon all page changes.', + 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', + 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', + 'watch_change_default' => 'Change default notification preferences', + 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_new' => 'Watching for new pages', + 'watch_detail_updates' => 'Watching new pages and updates', + 'watch_detail_comments' => 'Watching new pages, updates & comments', + 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_chapter' => 'Watching via parent chapter', + 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index 8e04a6cfa..c69c1d978 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -111,4 +111,6 @@ return [ // Settings & Maintenance 'maintenance_test_email_failure' => '寄送測試電子郵件時發生錯誤:', + // HTTP errors + 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', ]; diff --git a/lang/zh_TW/notifications.php b/lang/zh_TW/notifications.php new file mode 100644 index 000000000..5539ae9a9 --- /dev/null +++ b/lang/zh_TW/notifications.php @@ -0,0 +1,26 @@ + 'New comment on page: :pageName', + 'new_comment_intro' => 'A user has commented on a page in :appName:', + 'new_page_subject' => 'New page: :pageName', + 'new_page_intro' => 'A new page has been created in :appName:', + 'updated_page_subject' => 'Updated page: :pageName', + 'updated_page_intro' => 'A page has been updated in :appName:', + 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + + 'detail_page_name' => 'Page Name:', + 'detail_commenter' => 'Commenter:', + 'detail_comment' => 'Comment:', + 'detail_created_by' => 'Created By:', + 'detail_updated_by' => 'Updated By:', + + 'action_view_comment' => 'View Comment', + 'action_view_page' => 'View Page', + + 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', + 'footer_reason_link' => 'your notification preferences', +]; diff --git a/lang/zh_TW/preferences.php b/lang/zh_TW/preferences.php index ac137424d..a4fd757bf 100644 --- a/lang/zh_TW/preferences.php +++ b/lang/zh_TW/preferences.php @@ -5,6 +5,8 @@ */ return [ + 'preferences' => 'Preferences', + 'shortcuts' => '快捷鍵', 'shortcuts_interface' => '介面鍵盤快捷鍵', 'shortcuts_toggle_desc' => '您可以在此處啟用或停用鍵盤系統介面快捷鍵,這些快捷鍵用於導覽與操作。', @@ -15,4 +17,17 @@ return [ 'shortcuts_save' => '儲存快捷鍵', 'shortcuts_overlay_desc' => '注意:當快捷鍵啟用時,可以按下「?」來使用小幫手覆蓋畫面。這將會在目前的畫面上突顯可見動作的快捷鍵。', 'shortcuts_update_success' => '快捷鍵偏好設定已更新!', -]; \ No newline at end of file + 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', + 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + + 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', +]; diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 04ea8fdfc..cbedb2381 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -163,6 +163,7 @@ return [ 'role_manage_settings' => '管理應用程式設定', 'role_export_content' => '匯出內容', 'role_editor_change' => '重設頁面編輯器', + 'role_notifications' => 'Receive & manage notifications', 'role_asset' => '資源權限', 'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。', 'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆寫這裡的權限設定。', diff --git a/package-lock.json b/package-lock.json index 50987602f..8ad1f2323 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,45 +5,55 @@ "packages": { "": { "dependencies": { - "@codemirror/commands": "^6.2.2", - "@codemirror/lang-css": "^6.1.1", - "@codemirror/lang-html": "^6.4.3", - "@codemirror/lang-javascript": "^6.1.6", + "@codemirror/commands": "^6.2.4", + "@codemirror/lang-css": "^6.2.1", + "@codemirror/lang-html": "^6.4.5", + "@codemirror/lang-javascript": "^6.1.9", "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-markdown": "^6.1.1", + "@codemirror/lang-markdown": "^6.2.0", "@codemirror/lang-php": "^6.0.1", "@codemirror/lang-xml": "^6.0.2", - "@codemirror/language": "^6.6.0", - "@codemirror/legacy-modes": "^6.3.2", - "@codemirror/state": "^6.2.0", - "@codemirror/theme-one-dark": "^6.1.1", - "@codemirror/view": "^6.9.4", - "@lezer/highlight": "^1.1.4", + "@codemirror/language": "^6.9.0", + "@codemirror/legacy-modes": "^6.3.3", + "@codemirror/state": "^6.2.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.16.0", + "@lezer/highlight": "^1.1.6", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "codemirror": "^6.0.1", + "idb-keyval": "^6.2.1", "markdown-it": "^13.0.1", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.5.1", "sortablejs": "^1.15.0" }, "devDependencies": { - "@lezer/generator": "^1.2.2", + "@lezer/generator": "^1.4.2", "chokidar-cli": "^3.0", - "esbuild": "^0.17.16", - "eslint": "^8.38.0", + "esbuild": "^0.19", + "eslint": "^8.47.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.1", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.3.0", - "sass": "^1.62.0" + "sass": "^1.66.1" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@codemirror/autocomplete": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.5.1.tgz", - "integrity": "sha512-/Sv9yJmqyILbZ26U4LBHnAtbikuVxWUp+rQ8BXuRGtxZfbfKOY/WPbsUtvSP2h0ZUZMlkxV/hqbKRFzowlA6xw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.9.0.tgz", + "integrity": "sha512-Fbwm0V/Wn3BkEJZRhr0hi5BhCo5a7eBL6LYaliPjOSwCyfOpnjXY59HruSxOUNV+1OYer0Tgx1zRNQttjXyDog==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -58,9 +68,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.2.tgz", - "integrity": "sha512-s9lPVW7TxXrI/7voZ+HmD/yiAlwAYn9PH5SUVSUhsxXHhv4yl5eZ3KLntSoTynfdgVYM0oIpccQEWRBQgmNZyw==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.4.tgz", + "integrity": "sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.2.0", @@ -69,20 +79,21 @@ } }, "node_modules/@codemirror/lang-css": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.1.1.tgz", - "integrity": "sha512-P6jdNEHyRcqqDgbvHYyC9Wxkek0rnG3a9aVSRi4a7WrjPbQtBTaOmvYpXmm13zZMAatO4Oqpac+0QZs7sy+LnQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz", + "integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", "@lezer/css": "^1.0.0" } }, "node_modules/@codemirror/lang-html": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.3.tgz", - "integrity": "sha512-VKzQXEC8nL69Jg2hvAFPBwOdZNvL8tMFOrdFwWpU+wc6a6KEkndJ/19R5xSaglNX6v2bttm8uIEFYxdQDcIZVQ==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.5.tgz", + "integrity": "sha512-dUCSxkIw2G+chaUfw3Gfu5kkN83vJQN8gfQDp9iEHsIZluMJA0YJveT12zg/28BJx+uPsbQ6VimKCgx3oJrZxA==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", @@ -96,9 +107,9 @@ } }, "node_modules/@codemirror/lang-javascript": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.6.tgz", - "integrity": "sha512-TTK28z+vJQY9GAefLTDDptI2LMqMfAiuTpt8s9SsNwocjVQ1v9yTzfReMf1hYhspQCdhfa7fdKnQJ78mKe/bHQ==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.9.tgz", + "integrity": "sha512-z3jdkcqOEBT2txn2a87A0jSy6Te3679wg/U8QzMeftFt+4KA6QooMwfdFzJiuC3L6fXKfTXZcDocoaxMYfGz0w==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", @@ -119,10 +130,11 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.1.1.tgz", - "integrity": "sha512-n87Ms6Y5UYb1UkFu8sRzTLfq/yyF1y2AYiWvaVdbBQi5WDj1tFk5N+AKA+WC0Jcjc1VxvrCCM0iizjdYYi9sFQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.2.0.tgz", + "integrity": "sha512-deKegEQVzfBAcLPqsJEa+IxotqPVwWZi90UOEvQbfa01NTAw8jNinrykuYPTULGUj+gha0ZG2HBsn4s5d64Qrg==", "dependencies": { + "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", @@ -156,9 +168,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.6.0.tgz", - "integrity": "sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.0.tgz", + "integrity": "sha512-nFu311/0ne/qGuGCL3oKuktBgzVOaxCHZPZv1tLSZkNjPYxxvkjSbzno3MlErG2tgw1Yw1yF8BxMCegeMXqpiw==", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -169,9 +181,9 @@ } }, "node_modules/@codemirror/legacy-modes": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.3.2.tgz", - "integrity": "sha512-ki5sqNKWzKi5AKvpVE6Cna4Q+SgxYuYVLAZFSsMjGBWx5qSVa+D+xipix65GS3f2syTfAD9pXKMX4i4p49eneQ==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.3.3.tgz", + "integrity": "sha512-X0Z48odJ0KIoh/HY8Ltz75/4tDYc9msQf1E/2trlxFaFFhgjpVHjZ/BCXe1Lk7s4Gd67LL/CeEEHNI+xHOiESg==", "dependencies": { "@codemirror/language": "^6.0.0" } @@ -197,14 +209,14 @@ } }, "node_modules/@codemirror/state": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", - "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==" }, "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.1.tgz", - "integrity": "sha512-+CfzmScfJuD6uDF5bHJkAjWTQ2QAAHxODCPxUEgcImDYcJLT+4l5vLnBHmDVv46kCC5uUJGMrBJct2Z6JbvqyQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", + "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -213,9 +225,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.4.tgz", - "integrity": "sha512-Ov2H9gwlGUxiH94zWxlLtTlyogSFaQDIYjtSEcfzgh7MkKmKVchkmr4JbtR5zBev3jY5DVtKvUC8yjd1bKW55A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.16.0.tgz", + "integrity": "sha512-1Z2HkvkC3KR/oEZVuW9Ivmp8TWLzGEd8T8TA04TTwPvqogfkHBdYSlflytDOqmkUxM2d1ywTg7X2dU5mC+SXvg==", "dependencies": { "@codemirror/state": "^6.1.4", "style-mod": "^4.0.0", @@ -223,9 +235,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.17.tgz", - "integrity": "sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", + "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", "cpu": [ "arm" ], @@ -239,9 +251,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz", - "integrity": "sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", + "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", "cpu": [ "arm64" ], @@ -255,9 +267,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.17.tgz", - "integrity": "sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", + "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", "cpu": [ "x64" ], @@ -271,9 +283,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz", - "integrity": "sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", + "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", "cpu": [ "arm64" ], @@ -287,9 +299,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz", - "integrity": "sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", + "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", "cpu": [ "x64" ], @@ -303,9 +315,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz", - "integrity": "sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", + "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", "cpu": [ "arm64" ], @@ -319,9 +331,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz", - "integrity": "sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", + "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", "cpu": [ "x64" ], @@ -335,9 +347,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz", - "integrity": "sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", + "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", "cpu": [ "arm" ], @@ -351,9 +363,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz", - "integrity": "sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", + "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", "cpu": [ "arm64" ], @@ -367,9 +379,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz", - "integrity": "sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", + "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", "cpu": [ "ia32" ], @@ -383,9 +395,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz", - "integrity": "sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", + "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", "cpu": [ "loong64" ], @@ -399,9 +411,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz", - "integrity": "sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", + "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", "cpu": [ "mips64el" ], @@ -415,9 +427,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz", - "integrity": "sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", + "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", "cpu": [ "ppc64" ], @@ -431,9 +443,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz", - "integrity": "sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", + "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", "cpu": [ "riscv64" ], @@ -447,9 +459,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz", - "integrity": "sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", + "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", "cpu": [ "s390x" ], @@ -463,9 +475,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz", - "integrity": "sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", + "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", "cpu": [ "x64" ], @@ -479,9 +491,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz", - "integrity": "sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", + "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", "cpu": [ "x64" ], @@ -495,9 +507,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz", - "integrity": "sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", + "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", "cpu": [ "x64" ], @@ -511,9 +523,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz", - "integrity": "sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", + "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", "cpu": [ "x64" ], @@ -527,9 +539,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz", - "integrity": "sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", + "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", "cpu": [ "arm64" ], @@ -543,9 +555,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz", - "integrity": "sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", + "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", "cpu": [ "ia32" ], @@ -559,9 +571,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz", - "integrity": "sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", + "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", "cpu": [ "x64" ], @@ -590,23 +602,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -622,18 +634,18 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", + "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -678,22 +690,22 @@ } }, "node_modules/@lezer/generator": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.2.2.tgz", - "integrity": "sha512-O//eH9jTPM1GnbZruuD23xU68Pkuragonn1DEIom4Kt/eJN/QFt7Vzvp1YjV/XBmoUKC+2ySPgrA5fMF9FMM2g==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.4.2.tgz", + "integrity": "sha512-6fRwiGnx6Sv2I25YKeC5Mw9LtIA3Yq4FmVdRk0NP+M8tCsLUHSJd4KzrEzYqW3SKqOR/ZRlGiw+HAXMRygyCZA==", "dev": true, "dependencies": { "@lezer/common": "^1.0.2", "@lezer/lr": "^1.3.0" }, "bin": { - "lezer-generator": "dist/lezer-generator.cjs" + "lezer-generator": "src/lezer-generator.cjs" } }, "node_modules/@lezer/highlight": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.4.tgz", - "integrity": "sha512-IECkFmw2l7sFcYXrV8iT9GeY4W0fU4CxX0WMwhmhMIVjoDdD1Hr6q3G2NqVtLg/yVe5n7i4menG3tJ2r4eCrPQ==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", + "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", "dependencies": { "@lezer/common": "^1.0.0" } @@ -818,9 +830,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -925,6 +937,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -1348,9 +1379,9 @@ } }, "node_modules/esbuild": { - "version": "0.17.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.17.tgz", - "integrity": "sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", + "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1360,28 +1391,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.17", - "@esbuild/android-arm64": "0.17.17", - "@esbuild/android-x64": "0.17.17", - "@esbuild/darwin-arm64": "0.17.17", - "@esbuild/darwin-x64": "0.17.17", - "@esbuild/freebsd-arm64": "0.17.17", - "@esbuild/freebsd-x64": "0.17.17", - "@esbuild/linux-arm": "0.17.17", - "@esbuild/linux-arm64": "0.17.17", - "@esbuild/linux-ia32": "0.17.17", - "@esbuild/linux-loong64": "0.17.17", - "@esbuild/linux-mips64el": "0.17.17", - "@esbuild/linux-ppc64": "0.17.17", - "@esbuild/linux-riscv64": "0.17.17", - "@esbuild/linux-s390x": "0.17.17", - "@esbuild/linux-x64": "0.17.17", - "@esbuild/netbsd-x64": "0.17.17", - "@esbuild/openbsd-x64": "0.17.17", - "@esbuild/sunos-x64": "0.17.17", - "@esbuild/win32-arm64": "0.17.17", - "@esbuild/win32-ia32": "0.17.17", - "@esbuild/win32-x64": "0.17.17" + "@esbuild/android-arm": "0.19.2", + "@esbuild/android-arm64": "0.19.2", + "@esbuild/android-x64": "0.19.2", + "@esbuild/darwin-arm64": "0.19.2", + "@esbuild/darwin-x64": "0.19.2", + "@esbuild/freebsd-arm64": "0.19.2", + "@esbuild/freebsd-x64": "0.19.2", + "@esbuild/linux-arm": "0.19.2", + "@esbuild/linux-arm64": "0.19.2", + "@esbuild/linux-ia32": "0.19.2", + "@esbuild/linux-loong64": "0.19.2", + "@esbuild/linux-mips64el": "0.19.2", + "@esbuild/linux-ppc64": "0.19.2", + "@esbuild/linux-riscv64": "0.19.2", + "@esbuild/linux-s390x": "0.19.2", + "@esbuild/linux-x64": "0.19.2", + "@esbuild/netbsd-x64": "0.19.2", + "@esbuild/openbsd-x64": "0.19.2", + "@esbuild/sunos-x64": "0.19.2", + "@esbuild/win32-arm64": "0.19.2", + "@esbuild/win32-ia32": "0.19.2", + "@esbuild/win32-x64": "0.19.2" } }, "node_modules/escape-string-regexp": { @@ -1394,27 +1425,27 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", + "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "^8.47.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1422,22 +1453,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1470,9 +1498,9 @@ } }, "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1525,26 +1553,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", + "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, "dependencies": { "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", "array.prototype.flat": "^1.3.1", "array.prototype.flatmap": "^1.3.1", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", + "eslint-module-utils": "^2.8.0", "has": "^1.0.3", - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "is-glob": "^4.0.3", "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" @@ -1575,18 +1605,18 @@ } }, "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1600,9 +1630,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1856,14 +1886,14 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2068,13 +2098,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { @@ -2130,9 +2161,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2177,10 +2208,10 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/has": { @@ -2270,6 +2301,11 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2413,9 +2449,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2609,16 +2645,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2921,6 +2947,35 @@ "node": ">= 0.4" } }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -2948,17 +3003,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -3294,9 +3349,9 @@ } }, "node_modules/sass": { - "version": "1.62.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", - "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", + "version": "1.66.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", + "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -3311,9 +3366,9 @@ } }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -3727,15 +3782,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index 499e32833..21f2b1752 100644 --- a/package.json +++ b/package.json @@ -17,35 +17,36 @@ "fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\"" }, "devDependencies": { - "@lezer/generator": "^1.2.2", + "@lezer/generator": "^1.4.2", "chokidar-cli": "^3.0", - "esbuild": "^0.17.16", - "eslint": "^8.38.0", + "esbuild": "^0.19", + "eslint": "^8.47.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.1", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.3.0", - "sass": "^1.62.0" + "sass": "^1.66.1" }, "dependencies": { - "@codemirror/commands": "^6.2.2", - "@codemirror/lang-css": "^6.1.1", - "@codemirror/lang-html": "^6.4.3", - "@codemirror/lang-javascript": "^6.1.6", + "@codemirror/commands": "^6.2.4", + "@codemirror/lang-css": "^6.2.1", + "@codemirror/lang-html": "^6.4.5", + "@codemirror/lang-javascript": "^6.1.9", "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-markdown": "^6.1.1", + "@codemirror/lang-markdown": "^6.2.0", "@codemirror/lang-php": "^6.0.1", "@codemirror/lang-xml": "^6.0.2", - "@codemirror/language": "^6.6.0", - "@codemirror/legacy-modes": "^6.3.2", - "@codemirror/state": "^6.2.0", - "@codemirror/theme-one-dark": "^6.1.1", - "@codemirror/view": "^6.9.4", - "@lezer/highlight": "^1.1.4", + "@codemirror/language": "^6.9.0", + "@codemirror/legacy-modes": "^6.3.3", + "@codemirror/state": "^6.2.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.16.0", + "@lezer/highlight": "^1.1.6", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "codemirror": "^6.0.1", + "idb-keyval": "^6.2.1", "markdown-it": "^13.0.1", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.5.1", diff --git a/phpunit.xml b/phpunit.xml index 704372c5c..183e45637 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,6 +21,7 @@ + diff --git a/readme.md b/readme.md index 6c4811b39..aa335f9da 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,7 @@ [![Discord](https://img.shields.io/static/v1?label=Discord&message=chat&color=738adb&logo=discord)](https://discord.gg/ztkBqR2) [![Mastodon](https://img.shields.io/static/v1?label=Mastodon&message=@bookstack&color=595aff&logo=mastodon)](https://fosstodon.org/@bookstack) [![Twitter](https://img.shields.io/static/v1?label=Twitter&message=@bookstack_app&color=1d9bf0&logo=twitter)](https://twitter.com/bookstack_app) +[![PeerTube](https://img.shields.io/static/v1?label=PeerTube&message=bookstack@foss.video&color=f2690d&logo=peertube)](https://foss.video/c/bookstack) [![YouTube](https://img.shields.io/static/v1?label=YouTube&message=bookstackapp&color=ff0000&logo=youtube)](https://www.youtube.com/bookstackapp) A platform for storing and organising information and documentation. Details for BookStack can be found on the official website at https://www.bookstackapp.com/. @@ -50,8 +51,8 @@ Note: Listed services are not tested, vetted nor supported by the official BookS #### Bronze Sponsors - diff --git a/routes/web.php b/routes/web.php index 6e80635e0..8116cdaf8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -195,6 +195,9 @@ Route::middleware('auth')->group(function () { Route::post('/favourites/add', [ActivityControllers\FavouriteController::class, 'add']); Route::post('/favourites/remove', [ActivityControllers\FavouriteController::class, 'remove']); + // Watching + Route::put('/watching/update', [ActivityControllers\WatchController::class, 'update']); + // Other Pages Route::get('/', [HomeController::class, 'index']); Route::get('/home', [HomeController::class, 'index']); @@ -229,9 +232,11 @@ Route::middleware('auth')->group(function () { Route::delete('/settings/users/{id}', [UserControllers\UserController::class, 'destroy']); // User Preferences - Route::redirect('/preferences', '/'); + Route::get('/preferences', [UserControllers\UserPreferencesController::class, 'index']); Route::get('/preferences/shortcuts', [UserControllers\UserPreferencesController::class, 'showShortcuts']); Route::put('/preferences/shortcuts', [UserControllers\UserPreferencesController::class, 'updateShortcuts']); + Route::get('/preferences/notifications', [UserControllers\UserPreferencesController::class, 'showNotifications']); + Route::put('/preferences/notifications', [UserControllers\UserPreferencesController::class, 'updateNotifications']); Route::patch('/preferences/change-view/{type}', [UserControllers\UserPreferencesController::class, 'changeView']); Route::patch('/preferences/change-sort/{type}', [UserControllers\UserPreferencesController::class, 'changeSort']); Route::patch('/preferences/change-expansion/{type}', [UserControllers\UserPreferencesController::class, 'changeExpansion']); diff --git a/tests/Actions/WebhookCallTest.php b/tests/Actions/WebhookCallTest.php index fc49a524e..81bd7e7e8 100644 --- a/tests/Actions/WebhookCallTest.php +++ b/tests/Actions/WebhookCallTest.php @@ -7,11 +7,10 @@ use BookStack\Activity\DispatchWebhookJob; use BookStack\Activity\Models\Webhook; use BookStack\Activity\Tools\ActivityLogger; use BookStack\Api\ApiToken; -use BookStack\Entities\Models\PageRevision; use BookStack\Users\Models\User; -use Illuminate\Http\Client\Request; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Psr7\Response; use Illuminate\Support\Facades\Bus; -use Illuminate\Support\Facades\Http; use Tests\TestCase; class WebhookCallTest extends TestCase @@ -50,10 +49,10 @@ class WebhookCallTest extends TestCase public function test_webhook_runs_for_delete_actions() { + // This test must not fake the queue/bus since this covers an issue + // around handling and serialization of items now deleted from the database. $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']); - Http::fake([ - '*' => Http::response('', 500), - ]); + $this->mockHttpClient([new Response(500)]); $user = $this->users->newUser(); $resp = $this->asAdmin()->delete($user->getEditUrl()); @@ -69,9 +68,7 @@ class WebhookCallTest extends TestCase public function test_failed_webhook_call_logs_error() { $logger = $this->withTestLogger(); - Http::fake([ - '*' => Http::response('', 500), - ]); + $this->mockHttpClient([new Response(500)]); $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']); $this->assertNull($webhook->last_errored_at); @@ -86,7 +83,7 @@ class WebhookCallTest extends TestCase public function test_webhook_call_exception_is_caught_and_logged() { - Http::shouldReceive('asJson')->andThrow(new \Exception('Failed to perform request')); + $this->mockHttpClient([new ConnectException('Failed to perform request', new \GuzzleHttp\Psr7\Request('GET', ''))]); $logger = $this->withTestLogger(); $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']); @@ -101,31 +98,40 @@ class WebhookCallTest extends TestCase $this->assertNotNull($webhook->last_errored_at); } + public function test_webhook_uses_ssr_hosts_option_if_set() + { + config()->set('app.ssr_hosts', 'https://*.example.com'); + $responses = $this->mockHttpClient(); + + $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.co.uk'], ['all']); + $this->runEvent(ActivityType::ROLE_CREATE); + $this->assertEquals(0, $responses->requestCount()); + + $webhook->refresh(); + $this->assertEquals('The URL does not match the configured allowed SSR hosts', $webhook->last_error); + $this->assertNotNull($webhook->last_errored_at); + } + public function test_webhook_call_data_format() { - Http::fake([ - '*' => Http::response('', 200), - ]); + $responses = $this->mockHttpClient([new Response(200, [], '')]); $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']); $page = $this->entities->page(); $editor = $this->users->editor(); $this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor); - Http::assertSent(function (Request $request) use ($editor, $page, $webhook) { - $reqData = $request->data(); - - return $request->isJson() - && $reqData['event'] === 'page_update' - && $reqData['text'] === ($editor->name . ' updated page "' . $page->name . '"') - && is_string($reqData['triggered_at']) - && $reqData['triggered_by']['name'] === $editor->name - && $reqData['triggered_by_profile_url'] === $editor->getProfileUrl() - && $reqData['webhook_id'] === $webhook->id - && $reqData['webhook_name'] === $webhook->name - && $reqData['url'] === $page->getUrl() - && $reqData['related_item']['name'] === $page->name; - }); + $request = $responses->latestRequest(); + $reqData = json_decode($request->getBody(), true); + $this->assertEquals('page_update', $reqData['event']); + $this->assertEquals(($editor->name . ' updated page "' . $page->name . '"'), $reqData['text']); + $this->assertIsString($reqData['triggered_at']); + $this->assertEquals($editor->name, $reqData['triggered_by']['name']); + $this->assertEquals($editor->getProfileUrl(), $reqData['triggered_by_profile_url']); + $this->assertEquals($webhook->id, $reqData['webhook_id']); + $this->assertEquals($webhook->name, $reqData['webhook_name']); + $this->assertEquals($page->getUrl(), $reqData['url']); + $this->assertEquals($page->name, $reqData['related_item']['name']); } protected function runEvent(string $event, $detail = '', ?User $user = null) diff --git a/tests/Activity/WatchTest.php b/tests/Activity/WatchTest.php new file mode 100644 index 000000000..5b9ae5a4c --- /dev/null +++ b/tests/Activity/WatchTest.php @@ -0,0 +1,408 @@ +users->editor(); + $this->actingAs($editor); + + $entities = [$this->entities->book(), $this->entities->chapter(), $this->entities->page()]; + /** @var Entity $entity */ + foreach ($entities as $entity) { + $resp = $this->get($entity->getUrl()); + $this->withHtml($resp)->assertElementContains('form[action$="/watching/update"] button.icon-list-item', 'Watch'); + + $watchOptions = new UserEntityWatchOptions($editor, $entity); + $watchOptions->updateLevelByValue(WatchLevels::COMMENTS); + + $resp = $this->get($entity->getUrl()); + $this->withHtml($resp)->assertElementNotExists('form[action$="/watching/update"] button.icon-list-item'); + } + } + + public function test_watch_action_only_shows_with_permission() + { + $viewer = $this->users->viewer(); + $this->actingAs($viewer); + + $entities = [$this->entities->book(), $this->entities->chapter(), $this->entities->page()]; + /** @var Entity $entity */ + foreach ($entities as $entity) { + $resp = $this->get($entity->getUrl()); + $this->withHtml($resp)->assertElementNotExists('form[action$="/watching/update"] button.icon-list-item'); + } + + $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']); + + /** @var Entity $entity */ + foreach ($entities as $entity) { + $resp = $this->get($entity->getUrl()); + $this->withHtml($resp)->assertElementExists('form[action$="/watching/update"] button.icon-list-item'); + } + } + + public function test_watch_update() + { + $editor = $this->users->editor(); + $book = $this->entities->book(); + + $this->actingAs($editor)->get($book->getUrl()); + $resp = $this->put('/watching/update', [ + 'type' => $book->getMorphClass(), + 'id' => $book->id, + 'level' => 'comments' + ]); + + $resp->assertRedirect($book->getUrl()); + $this->assertSessionHas('success'); + $this->assertDatabaseHas('watches', [ + 'watchable_id' => $book->id, + 'watchable_type' => $book->getMorphClass(), + 'user_id' => $editor->id, + 'level' => WatchLevels::COMMENTS, + ]); + + $resp = $this->put('/watching/update', [ + 'type' => $book->getMorphClass(), + 'id' => $book->id, + 'level' => 'default' + ]); + $resp->assertRedirect($book->getUrl()); + $this->assertDatabaseMissing('watches', [ + 'watchable_id' => $book->id, + 'watchable_type' => $book->getMorphClass(), + 'user_id' => $editor->id, + ]); + } + + public function test_watch_update_fails_for_guest() + { + $this->setSettings(['app-public' => 'true']); + $guest = $this->users->guest(); + $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']); + $book = $this->entities->book(); + + $resp = $this->put('/watching/update', [ + 'type' => $book->getMorphClass(), + 'id' => $book->id, + 'level' => 'comments' + ]); + + $this->assertPermissionError($resp); + $guest->unsetRelations(); + } + + public function test_watch_detail_display_reflects_state() + { + $editor = $this->users->editor(); + $book = $this->entities->bookHasChaptersAndPages(); + $chapter = $book->chapters()->first(); + $page = $chapter->pages()->first(); + + (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::UPDATES); + + $this->actingAs($editor)->get($book->getUrl())->assertSee('Watching new pages and updates'); + $this->get($chapter->getUrl())->assertSee('Watching via parent book'); + $this->get($page->getUrl())->assertSee('Watching via parent book'); + + (new UserEntityWatchOptions($editor, $chapter))->updateLevelByValue(WatchLevels::COMMENTS); + $this->get($chapter->getUrl())->assertSee('Watching new pages, updates & comments'); + $this->get($page->getUrl())->assertSee('Watching via parent chapter'); + + (new UserEntityWatchOptions($editor, $page))->updateLevelByValue(WatchLevels::UPDATES); + $this->get($page->getUrl())->assertSee('Watching new pages and updates'); + } + + public function test_watch_detail_ignore_indicator_cascades() + { + $editor = $this->users->editor(); + $book = $this->entities->bookHasChaptersAndPages(); + (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE); + + $this->actingAs($editor)->get($book->getUrl())->assertSee('Ignoring notifications'); + $this->get($book->chapters()->first()->getUrl())->assertSee('Ignoring via parent book'); + $this->get($book->pages()->first()->getUrl())->assertSee('Ignoring via parent book'); + } + + public function test_watch_option_menu_shows_current_active_state() + { + $editor = $this->users->editor(); + $book = $this->entities->book(); + $options = new UserEntityWatchOptions($editor, $book); + + $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl())); + $respHtml->assertElementNotExists('form[action$="/watching/update"] svg[data-icon="check-circle"]'); + + $options->updateLevelByValue(WatchLevels::COMMENTS); + $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl())); + $respHtml->assertElementExists('form[action$="/watching/update"] button[value="comments"] svg[data-icon="check-circle"]'); + + $options->updateLevelByValue(WatchLevels::IGNORE); + $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl())); + $respHtml->assertElementExists('form[action$="/watching/update"] button[value="ignore"] svg[data-icon="check-circle"]'); + } + + public function test_watch_option_menu_limits_options_for_pages() + { + $editor = $this->users->editor(); + $book = $this->entities->bookHasChaptersAndPages(); + (new UserEntityWatchOptions($editor, $book))->updateLevelByValue(WatchLevels::IGNORE); + + $respHtml = $this->withHtml($this->actingAs($editor)->get($book->getUrl())); + $respHtml->assertElementExists('form[action$="/watching/update"] button[name="level"][value="new"]'); + + $respHtml = $this->withHtml($this->get($book->pages()->first()->getUrl())); + $respHtml->assertElementExists('form[action$="/watching/update"] button[name="level"][value="updates"]'); + $respHtml->assertElementNotExists('form[action$="/watching/update"] button[name="level"][value="new"]'); + } + + public function test_notify_own_page_changes() + { + $editor = $this->users->editor(); + $entities = $this->entities->createChainBelongingToUser($editor); + $prefs = new UserNotificationPreferences($editor); + $prefs->updateFromSettingsArray(['own-page-changes' => 'true']); + + $notifications = Notification::fake(); + + $this->asAdmin(); + $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']); + $notifications->assertSentTo($editor, PageUpdateNotification::class); + } + + public function test_notify_own_page_comments() + { + $editor = $this->users->editor(); + $entities = $this->entities->createChainBelongingToUser($editor); + $prefs = new UserNotificationPreferences($editor); + $prefs->updateFromSettingsArray(['own-page-comments' => 'true']); + + $notifications = Notification::fake(); + + $this->asAdmin()->post("/comment/{$entities['page']->id}", [ + 'text' => 'My new comment' + ]); + $notifications->assertSentTo($editor, CommentCreationNotification::class); + } + + public function test_notify_comment_replies() + { + $editor = $this->users->editor(); + $entities = $this->entities->createChainBelongingToUser($editor); + $prefs = new UserNotificationPreferences($editor); + $prefs->updateFromSettingsArray(['comment-replies' => 'true']); + + // Create some existing comments to pad IDs to help potentially error + // on mis-identification of parent via ids used. + Comment::factory()->count(5) + ->for($entities['page'], 'entity') + ->create(['created_by' => $this->users->admin()->id]); + + $notifications = Notification::fake(); + + $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [ + 'text' => 'My new comment' + ]); + $comment = $entities['page']->comments()->orderBy('id', 'desc')->first(); + + $this->asAdmin()->post("/comment/{$entities['page']->id}", [ + 'text' => 'My new comment response', + 'parent_id' => $comment->local_id, + ]); + $notifications->assertSentTo($editor, CommentCreationNotification::class); + } + + public function test_notify_watch_parent_book_ignore() + { + $editor = $this->users->editor(); + $entities = $this->entities->createChainBelongingToUser($editor); + $watches = new UserEntityWatchOptions($editor, $entities['book']); + $prefs = new UserNotificationPreferences($editor); + $watches->updateLevelByValue(WatchLevels::IGNORE); + $prefs->updateFromSettingsArray(['own-page-changes' => 'true', 'own-page-comments' => true]); + + $notifications = Notification::fake(); + + $this->asAdmin()->post("/comment/{$entities['page']->id}", [ + 'text' => 'My new comment response', + ]); + $this->entities->updatePage($entities['page'], ['name' => 'My updated page', 'html' => 'Hello']); + $notifications->assertNothingSent(); + } + + public function test_notify_watch_parent_book_comments() + { + $notifications = Notification::fake(); + $editor = $this->users->editor(); + $admin = $this->users->admin(); + $entities = $this->entities->createChainBelongingToUser($editor); + $watches = new UserEntityWatchOptions($editor, $entities['book']); + $watches->updateLevelByValue(WatchLevels::COMMENTS); + + // Comment post + $this->actingAs($admin)->post("/comment/{$entities['page']->id}", [ + 'text' => 'My new comment response', + ]); + + $notifications->assertSentTo($editor, function (CommentCreationNotification $notification) use ($editor, $admin, $entities) { + $mail = $notification->toMail($editor); + $mailContent = html_entity_decode(strip_tags($mail->render()), ENT_QUOTES); + return $mail->subject === 'New comment on page: ' . $entities['page']->getShortName() + && str_contains($mailContent, 'View Comment') + && str_contains($mailContent, 'Page Name: ' . $entities['page']->name) + && str_contains($mailContent, 'Commenter: ' . $admin->name) + && str_contains($mailContent, 'Comment: My new comment response'); + }); + } + + public function test_notify_watch_parent_book_updates() + { + $notifications = Notification::fake(); + $editor = $this->users->editor(); + $admin = $this->users->admin(); + $entities = $this->entities->createChainBelongingToUser($editor); + $watches = new UserEntityWatchOptions($editor, $entities['book']); + $watches->updateLevelByValue(WatchLevels::UPDATES); + + $this->actingAs($admin); + $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']); + + $notifications->assertSentTo($editor, function (PageUpdateNotification $notification) use ($editor, $admin) { + $mail = $notification->toMail($editor); + $mailContent = html_entity_decode(strip_tags($mail->render()), ENT_QUOTES); + return $mail->subject === 'Updated page: Updated page' + && str_contains($mailContent, 'View Page') + && str_contains($mailContent, 'Page Name: Updated page') + && str_contains($mailContent, 'Updated By: ' . $admin->name) + && str_contains($mailContent, 'you won\'t be sent notifications for further edits to this page by the same editor'); + }); + + // Test debounce + $notifications = Notification::fake(); + $this->entities->updatePage($entities['page'], ['name' => 'Updated page', 'html' => 'new page content']); + $notifications->assertNothingSentTo($editor); + } + + public function test_notify_watch_parent_book_new() + { + $notifications = Notification::fake(); + $editor = $this->users->editor(); + $admin = $this->users->admin(); + $entities = $this->entities->createChainBelongingToUser($editor); + $watches = new UserEntityWatchOptions($editor, $entities['book']); + $watches->updateLevelByValue(WatchLevels::NEW); + + $this->actingAs($admin)->get($entities['chapter']->getUrl('/create-page')); + $page = $entities['chapter']->pages()->where('draft', '=', true)->first(); + $this->post($page->getUrl(), ['name' => 'My new page', 'html' => 'My new page content']); + + $notifications->assertSentTo($editor, function (PageCreationNotification $notification) use ($editor, $admin) { + $mail = $notification->toMail($editor); + $mailContent = html_entity_decode(strip_tags($mail->render()), ENT_QUOTES); + return $mail->subject === 'New page: My new page' + && str_contains($mailContent, 'View Page') + && str_contains($mailContent, 'Page Name: My new page') + && str_contains($mailContent, 'Created By: ' . $admin->name); + }); + } + + public function test_notifications_sent_in_right_language() + { + $editor = $this->users->editor(); + $admin = $this->users->admin(); + setting()->putUser($editor, 'language', 'de'); + $entities = $this->entities->createChainBelongingToUser($editor); + $watches = new UserEntityWatchOptions($editor, $entities['book']); + $watches->updateLevelByValue(WatchLevels::COMMENTS); + + $activities = [ + ActivityType::PAGE_CREATE => $entities['page'], + ActivityType::PAGE_UPDATE => $entities['page'], + ActivityType::COMMENT_CREATE => (new Comment([]))->forceFill(['entity_id' => $entities['page']->id, 'entity_type' => $entities['page']->getMorphClass()]), + ]; + + $notifications = Notification::fake(); + $logger = app()->make(ActivityLogger::class); + $this->actingAs($admin); + + foreach ($activities as $activityType => $detail) { + $logger->add($activityType, $detail); + } + + $sent = $notifications->sentNotifications()[get_class($editor)][$editor->id]; + $this->assertCount(3, $sent); + + foreach ($sent as $notificationInfo) { + $notification = $notificationInfo[0]['notification']; + $this->assertInstanceOf(BaseActivityNotification::class, $notification); + $mail = $notification->toMail($editor); + $mailContent = html_entity_decode(strip_tags($mail->render()), ENT_QUOTES); + $this->assertStringContainsString('Name der Seite:', $mailContent); + $this->assertStringContainsString('Diese Benachrichtigung wurde', $mailContent); + $this->assertStringContainsString('Sollte es beim Anklicken der Schaltfläche', $mailContent); + } + } + + public function test_notifications_not_sent_if_lacking_view_permission_for_related_item() + { + $notifications = Notification::fake(); + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $watches = new UserEntityWatchOptions($editor, $page); + $watches->updateLevelByValue(WatchLevels::COMMENTS); + $this->permissions->disableEntityInheritedPermissions($page); + + $this->asAdmin()->post("/comment/{$page->id}", [ + 'text' => 'My new comment response', + ])->assertOk(); + + $notifications->assertNothingSentTo($editor); + } + + public function test_watches_deleted_on_user_delete() + { + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $watches = new UserEntityWatchOptions($editor, $page); + $watches->updateLevelByValue(WatchLevels::COMMENTS); + $this->assertDatabaseHas('watches', ['user_id' => $editor->id]); + + $this->asAdmin()->delete($editor->getEditUrl()); + + $this->assertDatabaseMissing('watches', ['user_id' => $editor->id]); + } + + public function test_watches_deleted_on_item_delete() + { + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $watches = new UserEntityWatchOptions($editor, $page); + $watches->updateLevelByValue(WatchLevels::COMMENTS); + $this->assertDatabaseHas('watches', ['watchable_type' => 'page', 'watchable_id' => $page->id]); + + $this->entities->destroy($page); + + $this->assertDatabaseMissing('watches', ['watchable_type' => 'page', 'watchable_id' => $page->id]); + } +} diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 713d8bba4..0629f3aed 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -45,6 +45,7 @@ class ChaptersApiTest extends TestCase 'value' => 'tagvalue', ], ], + 'priority' => 15, ]; $resp = $this->postJson($this->baseEndpoint, $details); @@ -137,6 +138,7 @@ class ChaptersApiTest extends TestCase 'value' => 'freshtagval', ], ], + 'priority' => 15, ]; $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details); diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index 4a81f738b..0d084472d 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -45,6 +45,7 @@ class PagesApiTest extends TestCase 'value' => 'tagvalue', ], ], + 'priority' => 15, ]; $resp = $this->postJson($this->baseEndpoint, $details); @@ -207,6 +208,7 @@ class PagesApiTest extends TestCase 'value' => 'freshtagval', ], ], + 'priority' => 15, ]; $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details); diff --git a/tests/Api/UsersApiTest.php b/tests/Api/UsersApiTest.php index e2a04b528..6ad727257 100644 --- a/tests/Api/UsersApiTest.php +++ b/tests/Api/UsersApiTest.php @@ -2,11 +2,11 @@ namespace Tests\Api; +use BookStack\Access\Notifications\UserInviteNotification; use BookStack\Activity\ActivityType; use BookStack\Activity\Models\Activity as ActivityModel; use BookStack\Entities\Models\Entity; use BookStack\Facades\Activity; -use BookStack\Notifications\UserInvite; use BookStack\Users\Models\Role; use BookStack\Users\Models\User; use Illuminate\Support\Facades\Hash; @@ -140,7 +140,7 @@ class UsersApiTest extends TestCase $resp->assertStatus(200); /** @var User $user */ $user = User::query()->where('email', '=', 'bboris@example.com')->first(); - Notification::assertSentTo($user, UserInvite::class); + Notification::assertSentTo($user, UserInviteNotification::class); } public function test_create_name_and_email_validation() diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php index 191a25f88..204a3bb5f 100644 --- a/tests/Auth/OidcTest.php +++ b/tests/Auth/OidcTest.php @@ -7,7 +7,6 @@ use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\Role; use BookStack\Users\Models\User; -use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use Illuminate\Testing\TestResponse; use Tests\Helpers\OidcJwtHelper; @@ -31,7 +30,7 @@ class OidcTest extends TestCase 'auth.method' => 'oidc', 'auth.defaults.guard' => 'oidc', 'oidc.name' => 'SingleSignOn-Testing', - 'oidc.display_name_claims' => ['name'], + 'oidc.display_name_claims' => 'name', 'oidc.client_id' => OidcJwtHelper::defaultClientId(), 'oidc.client_secret' => 'testpass', 'oidc.jwt_public_key' => $this->keyFilePath, @@ -137,7 +136,7 @@ class OidcTest extends TestCase $this->post('/oidc/login'); $state = session()->get('oidc_state'); - $transactions = &$this->mockHttpClient([$this->getMockAuthorizationResponse([ + $transactions = $this->mockHttpClient([$this->getMockAuthorizationResponse([ 'email' => 'benny@example.com', 'sub' => 'benny1010101', ])]); @@ -146,9 +145,8 @@ class OidcTest extends TestCase // App calls token endpoint to get id token $resp = $this->get('/oidc/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=' . $state); $resp->assertRedirect('/'); - $this->assertCount(1, $transactions); - /** @var Request $tokenRequest */ - $tokenRequest = $transactions[0]['request']; + $this->assertEquals(1, $transactions->requestCount()); + $tokenRequest = $transactions->latestRequest(); $this->assertEquals('https://oidc.local/token', (string) $tokenRequest->getUri()); $this->assertEquals('POST', $tokenRequest->getMethod()); $this->assertEquals('Basic ' . base64_encode(OidcJwtHelper::defaultClientId() . ':testpass'), $tokenRequest->getHeader('Authorization')[0]); @@ -279,7 +277,7 @@ class OidcTest extends TestCase { $this->withAutodiscovery(); - $transactions = &$this->mockHttpClient([ + $transactions = $this->mockHttpClient([ $this->getAutoDiscoveryResponse(), $this->getJwksResponse(), ]); @@ -289,11 +287,9 @@ class OidcTest extends TestCase $this->runLogin(); $this->assertTrue(auth()->check()); - /** @var Request $discoverRequest */ - $discoverRequest = $transactions[0]['request']; - /** @var Request $discoverRequest */ - $keysRequest = $transactions[1]['request']; + $discoverRequest = $transactions->requestAt(0); + $keysRequest = $transactions->requestAt(1); $this->assertEquals('GET', $keysRequest->getMethod()); $this->assertEquals('GET', $discoverRequest->getMethod()); $this->assertEquals(OidcJwtHelper::defaultIssuer() . '/.well-known/openid-configuration', $discoverRequest->getUri()); @@ -316,7 +312,7 @@ class OidcTest extends TestCase { $this->withAutodiscovery(); - $transactions = &$this->mockHttpClient([ + $transactions = $this->mockHttpClient([ $this->getAutoDiscoveryResponse(), $this->getJwksResponse(), $this->getAutoDiscoveryResponse([ @@ -327,15 +323,15 @@ class OidcTest extends TestCase // Initial run $this->post('/oidc/login'); - $this->assertCount(2, $transactions); + $this->assertEquals(2, $transactions->requestCount()); // Second run, hits cache $this->post('/oidc/login'); - $this->assertCount(2, $transactions); + $this->assertEquals(2, $transactions->requestCount()); // Third run, different issuer, new cache key config()->set(['oidc.issuer' => 'https://auto.example.com']); $this->post('/oidc/login'); - $this->assertCount(4, $transactions); + $this->assertEquals(4, $transactions->requestCount()); } public function test_auth_login_with_autodiscovery_with_keys_that_do_not_have_alg_property() @@ -412,6 +408,23 @@ class OidcTest extends TestCase $this->assertEquals('xXBennyTheGeezXx', $user->external_auth_id); } + public function test_auth_uses_mulitple_display_name_claims_if_configured() + { + config()->set(['oidc.display_name_claims' => 'first_name|last_name']); + + $this->runLogin([ + 'email' => 'benny@example.com', + 'sub' => 'benny1010101', + 'first_name' => 'Benny', + 'last_name' => 'Jenkins' + ]); + + $this->assertDatabaseHas('users', [ + 'name' => 'Benny Jenkins', + 'email' => 'benny@example.com', + ]); + } + public function test_login_group_sync() { config()->set([ diff --git a/tests/Auth/RegistrationTest.php b/tests/Auth/RegistrationTest.php index bc190afd8..ff1a9d66b 100644 --- a/tests/Auth/RegistrationTest.php +++ b/tests/Auth/RegistrationTest.php @@ -2,7 +2,7 @@ namespace Tests\Auth; -use BookStack\Notifications\ConfirmEmail; +use BookStack\Access\Notifications\ConfirmEmailNotification; use BookStack\Users\Models\Role; use BookStack\Users\Models\User; use Illuminate\Support\Facades\DB; @@ -28,7 +28,7 @@ class RegistrationTest extends TestCase // Ensure notification sent /** @var User $dbUser */ $dbUser = User::query()->where('email', '=', $user->email)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class); + Notification::assertSentTo($dbUser, ConfirmEmailNotification::class); // Test access and resend confirmation email $resp = $this->post('/login', ['email' => $user->email, 'password' => $user->password]); @@ -42,7 +42,7 @@ class RegistrationTest extends TestCase // Get confirmation and confirm notification matches $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) { + Notification::assertSentTo($dbUser, ConfirmEmailNotification::class, function ($notification, $channels) use ($emailConfirmation) { return $notification->token === $emailConfirmation->token; }); diff --git a/tests/Auth/ResetPasswordTest.php b/tests/Auth/ResetPasswordTest.php index b97a2f2d3..e60ac5643 100644 --- a/tests/Auth/ResetPasswordTest.php +++ b/tests/Auth/ResetPasswordTest.php @@ -2,7 +2,7 @@ namespace Tests\Auth; -use BookStack\Notifications\ResetPassword; +use BookStack\Access\Notifications\ResetPasswordNotification; use BookStack\Users\Models\User; use Illuminate\Support\Facades\Notification; use Tests\TestCase; @@ -34,8 +34,8 @@ class ResetPasswordTest extends TestCase /** @var User $user */ $user = User::query()->where('email', '=', 'admin@admin.com')->first(); - Notification::assertSentTo($user, ResetPassword::class); - $n = Notification::sent($user, ResetPassword::class); + Notification::assertSentTo($user, ResetPasswordNotification::class); + $n = Notification::sent($user, ResetPasswordNotification::class); $this->get('/password/reset/' . $n->first()->token) ->assertOk() @@ -95,7 +95,7 @@ class ResetPasswordTest extends TestCase $resp = $this->followingRedirects()->post('/password/email', [ 'email' => $editor->email, ]); - Notification::assertTimesSent(1, ResetPassword::class); + Notification::assertTimesSent(1, ResetPasswordNotification::class); $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); } } diff --git a/tests/Auth/UserInviteTest.php b/tests/Auth/UserInviteTest.php index 8d6143877..a9dee0007 100644 --- a/tests/Auth/UserInviteTest.php +++ b/tests/Auth/UserInviteTest.php @@ -2,8 +2,8 @@ namespace Tests\Auth; +use BookStack\Access\Notifications\UserInviteNotification; use BookStack\Access\UserInviteService; -use BookStack\Notifications\UserInvite; use BookStack\Users\Models\User; use Carbon\Carbon; use Illuminate\Notifications\Messages\MailMessage; @@ -29,7 +29,7 @@ class UserInviteTest extends TestCase $newUser = User::query()->where('email', '=', $email)->orderBy('id', 'desc')->first(); - Notification::assertSentTo($newUser, UserInvite::class); + Notification::assertSentTo($newUser, UserInviteNotification::class); $this->assertDatabaseHas('user_invites', [ 'user_id' => $newUser->id, ]); @@ -50,7 +50,7 @@ class UserInviteTest extends TestCase $resp->assertRedirect('/settings/users'); $newUser = User::query()->where('email', '=', $email)->orderBy('id', 'desc')->first(); - Notification::assertSentTo($newUser, UserInvite::class, function ($notification, $channels, $notifiable) { + Notification::assertSentTo($newUser, UserInviteNotification::class, function ($notification, $channels, $notifiable) { /** @var MailMessage $mail */ $mail = $notification->toMail($notifiable); diff --git a/tests/Commands/CleanupImagesCommandTest.php b/tests/Commands/CleanupImagesCommandTest.php index a1a5ab985..36fd51e96 100644 --- a/tests/Commands/CleanupImagesCommandTest.php +++ b/tests/Commands/CleanupImagesCommandTest.php @@ -14,7 +14,7 @@ class CleanupImagesCommandTest extends TestCase $this->artisan('bookstack:cleanup-images -v') ->expectsOutput('Dry run, no images have been deleted') - ->expectsOutput('1 images found that would have been deleted') + ->expectsOutput('1 image(s) found that would have been deleted') ->expectsOutputToContain($image->path) ->assertExitCode(0); @@ -29,7 +29,7 @@ class CleanupImagesCommandTest extends TestCase $this->artisan('bookstack:cleanup-images --force') ->expectsOutputToContain('This operation is destructive and is not guaranteed to be fully accurate') ->expectsConfirmation('Are you sure you want to proceed?', 'yes') - ->expectsOutput('1 images deleted') + ->expectsOutput('1 image(s) deleted') ->assertExitCode(0); $this->assertDatabaseMissing('images', ['id' => $image->id]); @@ -46,4 +46,17 @@ class CleanupImagesCommandTest extends TestCase $this->assertDatabaseHas('images', ['id' => $image->id]); } + + public function test_command_force_no_interaction_run() + { + $page = $this->entities->page(); + $image = Image::factory()->create(['uploaded_to' => $page->id]); + + $this->artisan('bookstack:cleanup-images --force --no-interaction') + ->expectsOutputToContain('This operation is destructive and is not guaranteed to be fully accurate') + ->expectsOutput('1 image(s) deleted') + ->assertExitCode(0); + + $this->assertDatabaseMissing('images', ['id' => $image->id]); + } } diff --git a/tests/Commands/RefreshAvatarCommandTest.php b/tests/Commands/RefreshAvatarCommandTest.php new file mode 100644 index 000000000..6126f21a8 --- /dev/null +++ b/tests/Commands/RefreshAvatarCommandTest.php @@ -0,0 +1,246 @@ +set([ + 'services.disable_services' => false, + 'services.avatar_url' => 'https://avatars.example.com?a=b', + ]); + } + + public function test_command_errors_if_avatar_fetch_disabled() + { + config()->set(['services.avatar_url' => false]); + + $this->artisan('bookstack:refresh-avatar') + ->expectsOutputToContain("Avatar fetching is disabled on this instance") + ->assertExitCode(1); + } + + public function test_command_requires_email_or_id_option() + { + $this->artisan('bookstack:refresh-avatar') + ->expectsOutputToContain("Either a --id= or --email= option must be provided") + ->assertExitCode(1); + } + + public function test_command_runs_with_provided_email() + { + $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]); + + $user = $this->users->viewer(); + $this->assertFalse($user->avatar()->exists()); + + $this->artisan("bookstack:refresh-avatar --email={$user->email} -f") + ->expectsQuestion('Are you sure you want to proceed?', true) + ->expectsOutput("[ID: {$user->id}] {$user->email} - Updated") + ->expectsOutputToContain('This will destroy any existing avatar images these users have, and attempt to fetch new avatar images from avatars.example.com') + ->assertExitCode(0); + + $this->assertEquals('https://avatars.example.com?a=b', $requests->latestRequest()->getUri()); + + $user->refresh(); + $this->assertTrue($user->avatar()->exists()); + } + + public function test_command_runs_with_provided_id() + { + $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]); + + $user = $this->users->viewer(); + $this->assertFalse($user->avatar()->exists()); + + $this->artisan("bookstack:refresh-avatar --id={$user->id} -f") + ->expectsQuestion('Are you sure you want to proceed?', true) + ->expectsOutput("[ID: {$user->id}] {$user->email} - Updated") + ->assertExitCode(0); + + $this->assertEquals('https://avatars.example.com?a=b', $requests->latestRequest()->getUri()); + + $user->refresh(); + $this->assertTrue($user->avatar()->exists()); + } + + public function test_command_runs_with_provided_id_error_upstream() + { + $requests = $this->mockHttpClient([new Response(404)]); + + $user = $this->users->viewer(); + $this->assertFalse($user->avatar()->exists()); + + $this->artisan("bookstack:refresh-avatar --id={$user->id} -f") + ->expectsQuestion('Are you sure you want to proceed?', true) + ->expectsOutput("[ID: {$user->id}] {$user->email} - Not updated") + ->assertExitCode(1); + + $this->assertEquals(1, $requests->requestCount()); + $this->assertFalse($user->avatar()->exists()); + } + + public function test_saying_no_to_confirmation_does_not_refresh_avatar() + { + $user = $this->users->viewer(); + + $this->assertFalse($user->avatar()->exists()); + $this->artisan("bookstack:refresh-avatar --id={$user->id} -f") + ->expectsQuestion('Are you sure you want to proceed?', false) + ->assertExitCode(0); + $this->assertFalse($user->avatar()->exists()); + } + + public function test_giving_non_existing_user_shows_error_message() + { + $this->artisan('bookstack:refresh-avatar --email=donkeys@example.com') + ->expectsOutput('A user where email=donkeys@example.com could not be found.') + ->assertExitCode(1); + } + + public function test_command_runs_all_users_without_avatars_dry_run() + { + $users = User::query()->where('image_id', '=', 0)->get(); + + $this->artisan('bookstack:refresh-avatar --users-without-avatars') + ->expectsOutput(count($users) . ' user(s) found to update avatars for.') + ->expectsOutput("[ID: {$users[0]->id}] {$users[0]->email} - Not updated") + ->expectsOutput('Dry run, no avatars were updated.') + ->assertExitCode(0); + } + + public function test_command_runs_all_users_without_avatars_with_none_to_update() + { + $requests = $this->mockHttpClient(); + $image = Image::factory()->create(); + User::query()->update(['image_id' => $image->id]); + + $this->artisan('bookstack:refresh-avatar --users-without-avatars -f') + ->expectsOutput('0 user(s) found to update avatars for.') + ->assertExitCode(0); + + $this->assertEquals(0, $requests->requestCount()); + } + + public function test_command_runs_all_users_without_avatars() + { + /** @var Collection|User[] $users */ + $users = User::query()->where('image_id', '=', 0)->get(); + + $pendingCommand = $this->artisan('bookstack:refresh-avatar --users-without-avatars -f'); + $pendingCommand + ->expectsOutput($users->count() . ' user(s) found to update avatars for.') + ->expectsQuestion('Are you sure you want to proceed?', true); + + $responses = []; + foreach ($users as $user) { + $pendingCommand->expectsOutput("[ID: {$user->id}] {$user->email} - Updated"); + $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData()); + } + $requests = $this->mockHttpClient($responses); + + $pendingCommand->assertExitCode(0); + $pendingCommand->run(); + + $this->assertEquals(0, User::query()->where('image_id', '=', 0)->count()); + $this->assertEquals($users->count(), $requests->requestCount()); + } + + public function test_saying_no_to_confirmation_all_users_without_avatars() + { + $requests = $this->mockHttpClient(); + + $this->artisan('bookstack:refresh-avatar --users-without-avatars -f') + ->expectsQuestion('Are you sure you want to proceed?', false) + ->assertExitCode(0); + + $this->assertEquals(0, $requests->requestCount()); + } + + public function test_command_runs_all_users_dry_run() + { + $users = User::query()->where('image_id', '=', 0)->get(); + + $this->artisan('bookstack:refresh-avatar --all') + ->expectsOutput(count($users) . ' user(s) found to update avatars for.') + ->expectsOutput("[ID: {$users[0]->id}] {$users[0]->email} - Not updated") + ->expectsOutput('Dry run, no avatars were updated.') + ->assertExitCode(0); + } + + public function test_command_runs_update_all_users_avatar() + { + /** @var Collection|User[] $users */ + $users = User::query()->get(); + + $pendingCommand = $this->artisan('bookstack:refresh-avatar --all -f'); + $pendingCommand + ->expectsOutput($users->count() . ' user(s) found to update avatars for.') + ->expectsQuestion('Are you sure you want to proceed?', true); + + $responses = []; + foreach ($users as $user) { + $pendingCommand->expectsOutput("[ID: {$user->id}] {$user->email} - Updated"); + $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData()); + } + $requests = $this->mockHttpClient($responses); + + $pendingCommand->assertExitCode(0); + $pendingCommand->run(); + + $this->assertEquals(0, User::query()->where('image_id', '=', 0)->count()); + $this->assertEquals($users->count(), $requests->requestCount()); + } + + public function test_command_runs_update_all_users_avatar_errors() + { + /** @var Collection|User[] $users */ + $users = array_values(User::query()->get()->all()); + + $pendingCommand = $this->artisan('bookstack:refresh-avatar --all -f'); + $pendingCommand + ->expectsOutput(count($users) . ' user(s) found to update avatars for.') + ->expectsQuestion('Are you sure you want to proceed?', true); + + $responses = []; + foreach ($users as $index => $user) { + if ($index === 0) { + $pendingCommand->expectsOutput("[ID: {$user->id}] {$user->email} - Not updated"); + $responses[] = new Response(404); + continue; + } + + $pendingCommand->expectsOutput("[ID: {$user->id}] {$user->email} - Updated"); + $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData()); + } + + $requests = $this->mockHttpClient($responses); + + $pendingCommand->assertExitCode(1); + $pendingCommand->run(); + + $userWithAvatars = User::query()->where('image_id', '!=', 0)->count(); + $this->assertEquals(count($users) - 1, $userWithAvatars); + $this->assertEquals(count($users), $requests->requestCount()); + } + + public function test_saying_no_to_confirmation_update_all_users_avatar() + { + $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]); + + $this->artisan('bookstack:refresh-avatar --all -f') + ->expectsQuestion('Are you sure you want to proceed?', false) + ->assertExitCode(0); + + $this->assertEquals(0, $requests->requestCount()); + } +} diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index 0a71bb6ef..23fc68197 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -152,4 +152,16 @@ class CommentTest extends TestCase $respHtml = $this->withHtml($this->get($page->getUrl('/edit'))); $respHtml->assertElementContains('.comment-box .content', 'My great comment to see in the editor'); } + + public function test_comment_creator_name_truncated() + { + [$longNamedUser] = $this->users->newUserWithRole(['name' => 'Wolfeschlegelsteinhausenbergerdorff'], ['comment-create-all', 'page-view-all']); + $page = $this->entities->page(); + + $comment = Comment::factory()->make(); + $this->actingAs($longNamedUser)->postJson("/comment/$page->id", $comment->getAttributes()); + + $pageResp = $this->asAdmin()->get($page->getUrl()); + $pageResp->assertSee('Wolfeschlegels…'); + } } diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 97d5a6664..a272dc38b 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -136,7 +136,7 @@ class PageRevisionTest extends TestCase $page = $this->entities->page(); $this->createRevisions($page, 2); - $pageView = $this->get($page->getUrl()); + $pageView = $this->asViewer()->get($page->getUrl()); $pageView->assertSee('Revision #' . $page->revision_count); } diff --git a/tests/Entity/SearchOptionsTest.php b/tests/Entity/SearchOptionsTest.php index cac9c67f1..8bc9d02e4 100644 --- a/tests/Entity/SearchOptionsTest.php +++ b/tests/Entity/SearchOptionsTest.php @@ -3,6 +3,7 @@ namespace Tests\Entity; use BookStack\Search\SearchOptions; +use Illuminate\Http\Request; use Tests\TestCase; class SearchOptionsTest extends TestCase @@ -17,6 +18,13 @@ class SearchOptionsTest extends TestCase $this->assertEquals(['is_tree' => ''], $options->filters); } + public function test_from_string_properly_parses_escaped_quotes() + { + $options = SearchOptions::fromString('"\"cat\"" surprise "\"\"" "\"donkey" "\""'); + + $this->assertEquals(['"cat"', '""', '"donkey', '"'], $options->exacts); + } + public function test_to_string_includes_all_items_in_the_correct_format() { $expected = 'cat "dog" [tag=good] {is_tree}'; @@ -32,6 +40,15 @@ class SearchOptionsTest extends TestCase } } + public function test_to_string_escapes_quotes_as_expected() + { + $options = new SearchOptions(); + $options->exacts = ['"cat"', '""', '"donkey', '"']; + + $output = $options->toString(); + $this->assertEquals('"\"cat\"" "\"\"" "\"donkey" "\""', $output); + } + public function test_correct_filter_values_are_set_from_string() { $opts = SearchOptions::fromString('{is_tree} {name:dan} {cat:happy}'); @@ -42,4 +59,22 @@ class SearchOptionsTest extends TestCase 'cat' => 'happy', ], $opts->filters); } + public function test_it_cannot_parse_out_empty_exacts() + { + $options = SearchOptions::fromString('"" test ""'); + + $this->assertEmpty($options->exacts); + $this->assertCount(1, $options->searches); + } + + public function test_from_request_properly_parses_exacts_from_search_terms() + { + $request = new Request([ + 'search' => 'biscuits "cheese" "" "baked beans"' + ]); + + $options = SearchOptions::fromRequest($request); + $this->assertEquals(["biscuits"], $options->searches); + $this->assertEquals(['"cheese"', '""', '"baked', 'beans"'], $options->exacts); + } } diff --git a/tests/FavouriteTest.php b/tests/FavouriteTest.php index 0e30cbd58..48048e284 100644 --- a/tests/FavouriteTest.php +++ b/tests/FavouriteTest.php @@ -14,10 +14,10 @@ class FavouriteTest extends TestCase $resp = $this->actingAs($editor)->get($page->getUrl()); $this->withHtml($resp)->assertElementContains('button', 'Favourite'); - $this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/add"]'); + $this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/add"] input[name="type"][value="page"]'); $resp = $this->post('/favourites/add', [ - 'type' => get_class($page), + 'type' => $page->getMorphClass(), 'id' => $page->id, ]); $resp->assertRedirect($page->getUrl()); @@ -45,7 +45,7 @@ class FavouriteTest extends TestCase $this->withHtml($resp)->assertElementExists('form[method="POST"][action$="/favourites/remove"]'); $resp = $this->post('/favourites/remove', [ - 'type' => get_class($page), + 'type' => $page->getMorphClass(), 'id' => $page->id, ]); $resp->assertRedirect($page->getUrl()); @@ -67,7 +67,7 @@ class FavouriteTest extends TestCase $this->actingAs($user)->get($book->getUrl()); $resp = $this->post('/favourites/add', [ - 'type' => get_class($book), + 'type' => $book->getMorphClass(), 'id' => $book->id, ]); $resp->assertRedirect($book->getUrl()); diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php index ddc854290..3cb8c44d3 100644 --- a/tests/Helpers/EntityProvider.php +++ b/tests/Helpers/EntityProvider.php @@ -11,6 +11,7 @@ use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Entities\Repos\PageRepo; +use BookStack\Entities\Tools\TrashCan; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; @@ -197,6 +198,16 @@ class EntityProvider return $draftPage; } + /** + * Fully destroy the given entity from the system, bypassing the recycle bin + * stage. Still runs through main app deletion logic. + */ + public function destroy(Entity $entity) + { + $trash = app()->make(TrashCan::class); + $trash->destroyEntity($entity); + } + /** * @param Entity|Entity[] $entities */ diff --git a/tests/Helpers/UserRoleProvider.php b/tests/Helpers/UserRoleProvider.php index b86e90394..fe19cad4a 100644 --- a/tests/Helpers/UserRoleProvider.php +++ b/tests/Helpers/UserRoleProvider.php @@ -50,6 +50,14 @@ class UserRoleProvider return $user; } + /** + * Get the system "guest" user. + */ + public function guest(): User + { + return User::getGuest(); + } + /** * Create a new fresh user without any relations. */ diff --git a/tests/LanguageTest.php b/tests/LanguageTest.php index a66227ff2..6b6856184 100644 --- a/tests/LanguageTest.php +++ b/tests/LanguageTest.php @@ -3,6 +3,7 @@ namespace Tests; use BookStack\Activity\ActivityType; +use BookStack\Translation\LocaleManager; class LanguageTest extends TestCase { @@ -17,12 +18,12 @@ class LanguageTest extends TestCase $this->langs = array_diff(scandir(lang_path('')), ['..', '.']); } - public function test_locales_config_key_set_properly() + public function test_locales_list_set_properly() { - $configLocales = config('app.locales'); - sort($configLocales); + $appLocales = $this->app->make(LocaleManager::class)->getAllAppLocales(); + sort($appLocales); sort($this->langs); - $this->assertEquals(implode(':', $configLocales), implode(':', $this->langs), 'app.locales configuration variable does not match those found in lang files'); + $this->assertEquals(implode(':', $this->langs), implode(':', $appLocales), 'app.locales configuration variable does not match those found in lang files'); } // Not part of standard phpunit test runs since we sometimes expect non-added langs. @@ -75,13 +76,13 @@ class LanguageTest extends TestCase } } - public function test_rtl_config_set_if_lang_is_rtl() + public function test_views_use_rtl_if_rtl_language_is_set() { - $this->asEditor(); - $this->assertFalse(config('app.rtl'), 'App RTL config should be false by default'); + $this->asEditor()->withHtml($this->get('/'))->assertElementExists('html[dir="ltr"]'); + setting()->putUser($this->users->editor(), 'language', 'ar'); - $this->get('/'); - $this->assertTrue(config('app.rtl'), 'App RTL config should have been set to true by middleware'); + + $this->withHtml($this->get('/'))->assertElementExists('html[dir="rtl"]'); } public function test_unknown_lang_does_not_break_app() diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php index 6f0e2f1d3..875b279a8 100644 --- a/tests/PublicActionTest.php +++ b/tests/PublicActionTest.php @@ -103,7 +103,7 @@ class PublicActionTest extends TestCase $resp = $this->post($chapter->getUrl('/create-guest-page'), ['name' => 'My guest page']); $resp->assertRedirect($chapter->book->getUrl('/page/my-guest-page/edit')); - $user = User::getDefault(); + $user = $this->users->guest(); $this->assertDatabaseHas('pages', [ 'name' => 'My guest page', 'chapter_id' => $chapter->id, @@ -197,7 +197,7 @@ class PublicActionTest extends TestCase public function test_public_view_can_take_on_other_roles() { $this->setSettings(['app-public' => 'true']); - $newRole = $this->users->attachNewRole(User::getDefault(), []); + $newRole = $this->users->attachNewRole($this->users->guest(), []); $page = $this->entities->page(); $this->permissions->disableEntityInheritedPermissions($page); $this->permissions->addEntityPermission($page, ['view', 'update'], $newRole); @@ -207,4 +207,16 @@ class PublicActionTest extends TestCase $this->withHtml($resp)->assertLinkExists($page->getUrl('/edit')); } + + public function test_public_user_cannot_view_or_update_their_profile() + { + $this->setSettings(['app-public' => 'true']); + $guest = $this->users->guest(); + + $resp = $this->get($guest->getEditUrl()); + $this->assertPermissionError($resp); + + $resp = $this->put($guest->getEditUrl(), ['name' => 'My new guest name']); + $this->assertPermissionError($resp); + } } diff --git a/tests/Settings/TestEmailTest.php b/tests/Settings/TestEmailTest.php index 322f90107..e96024e7b 100644 --- a/tests/Settings/TestEmailTest.php +++ b/tests/Settings/TestEmailTest.php @@ -2,7 +2,7 @@ namespace Tests\Settings; -use BookStack\Notifications\TestEmail; +use BookStack\Settings\TestEmailNotification; use Illuminate\Contracts\Notifications\Dispatcher; use Illuminate\Support\Facades\Notification; use Tests\TestCase; @@ -26,7 +26,7 @@ class TestEmailTest extends TestCase $sendReq->assertRedirect('/settings/maintenance#image-cleanup'); $this->assertSessionHas('success', 'Email sent to ' . $admin->email); - Notification::assertSentTo($admin, TestEmail::class); + Notification::assertSentTo($admin, TestEmailNotification::class); } public function test_send_test_email_failure_displays_error_notification() @@ -57,6 +57,6 @@ class TestEmailTest extends TestCase $this->permissions->grantUserRolePermissions($user, ['settings-manage']); $sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email'); - Notification::assertSentTo($user, TestEmail::class); + Notification::assertSentTo($user, TestEmailNotification::class); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 322ab0370..c59f843e9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,12 +3,10 @@ namespace Tests; use BookStack\Entities\Models\Entity; +use BookStack\Http\HttpClientHistory; +use BookStack\Http\HttpRequestService; use BookStack\Settings\SettingService; -use BookStack\Uploads\HttpFetcher; -use GuzzleHttp\Client; -use GuzzleHttp\Handler\MockHandler; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Middleware; +use BookStack\Users\Models\User; use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; @@ -17,10 +15,8 @@ use Illuminate\Support\Env; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Testing\Assert as PHPUnit; -use Mockery; use Monolog\Handler\TestHandler; use Monolog\Logger; -use Psr\Http\Client\ClientInterface; use Ssddanbrown\AssertHtml\TestsHtml; use Tests\Helpers\EntityProvider; use Tests\Helpers\FileProvider; @@ -109,33 +105,11 @@ abstract class TestCase extends BaseTestCase } /** - * Mock the HttpFetcher service and return the given data on fetch. + * Mock the http client used in BookStack http calls. */ - protected function mockHttpFetch($returnData, int $times = 1) + protected function mockHttpClient(array $responses = []): HttpClientHistory { - $mockHttp = Mockery::mock(HttpFetcher::class); - $this->app[HttpFetcher::class] = $mockHttp; - $mockHttp->shouldReceive('fetch') - ->times($times) - ->andReturn($returnData); - } - - /** - * Mock the http client used in BookStack. - * Returns a reference to the container which holds all history of http transactions. - * - * @link https://docs.guzzlephp.org/en/stable/testing.html#history-middleware - */ - protected function &mockHttpClient(array $responses = []): array - { - $container = []; - $history = Middleware::history($container); - $mock = new MockHandler($responses); - $handlerStack = new HandlerStack($mock); - $handlerStack->push($history); - $this->app[ClientInterface::class] = new Client(['handler' => $handlerStack]); - - return $container; + return $this->app->make(HttpRequestService::class)->mockClient($responses); } /** diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 6976f2384..f0266cd0c 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -8,17 +8,15 @@ use BookStack\Activity\Models\Webhook; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Tools\PageContent; +use BookStack\Exceptions\ThemeException; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; use Illuminate\Console\Command; -use Illuminate\Http\Client\Request as HttpClientRequest; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; -use Illuminate\Support\Facades\Http; -use League\CommonMark\ConfigurableEnvironmentInterface; use League\CommonMark\Environment\Environment; class ThemeTest extends TestCase @@ -54,6 +52,19 @@ class ThemeTest extends TestCase }); } + public function test_theme_functions_loads_errors_are_caught_and_logged() + { + $this->usingThemeFolder(function ($themeFolder) { + $functionsFile = theme_path('functions.php'); + file_put_contents($functionsFile, "expectException(ThemeException::class); + $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/'); + + $this->runWithEnv('APP_THEME', $themeFolder, fn() => null); + }); + } + public function test_event_commonmark_environment_configure() { $callbackCalled = false; @@ -177,9 +188,7 @@ class ThemeTest extends TestCase }; Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, $callback); - Http::fake([ - '*' => Http::response('', 200), - ]); + $responses = $this->mockHttpClient([new \GuzzleHttp\Psr7\Response(200, [], '')]); $webhook = new Webhook(['name' => 'Test webhook', 'endpoint' => 'https://example.com']); $webhook->save(); @@ -193,9 +202,10 @@ class ThemeTest extends TestCase $this->assertEquals($webhook->id, $args[1]->id); $this->assertEquals($detail->id, $args[2]->id); - Http::assertSent(function (HttpClientRequest $request) { - return $request->isJson() && $request->data()['test'] === 'hello!'; - }); + $this->assertEquals(1, $responses->requestCount()); + $request = $responses->latestRequest(); + $reqData = json_decode($request->getBody(), true); + $this->assertEquals('hello!', $reqData['test']); } public function test_event_activity_logged() diff --git a/tests/Unit/SsrUrlValidatorTest.php b/tests/Unit/SsrUrlValidatorTest.php new file mode 100644 index 000000000..8fb538916 --- /dev/null +++ b/tests/Unit/SsrUrlValidatorTest.php @@ -0,0 +1,62 @@ + '', 'url' => '', 'result' => false], + ['config' => '', 'url' => 'https://example.com', 'result' => false], + ['config' => ' ', 'url' => 'https://example.com', 'result' => false], + ['config' => '*', 'url' => '', 'result' => false], + ['config' => '*', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*', 'url' => 'https://example.com', 'result' => true], + ['config' => 'http://*', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://*example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*ample.com', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*.example.com', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://*.example.com', 'url' => 'https://test.example.com', 'result' => true], + ['config' => '*//example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => '*//example.com', 'url' => 'http://example.com', 'result' => true], + ['config' => '*//example.co', 'url' => 'http://example.co.uk', 'result' => false], + ['config' => '*//example.co/bookstack', 'url' => 'https://example.co/bookstack/a/path', 'result' => true], + ['config' => '*//example.co*', 'url' => 'https://example.co.uk/bookstack/a/path', 'result' => true], + ['config' => 'https://example.com', 'url' => 'https://example.com/a/b/c?test=cat', 'result' => true], + ['config' => 'https://example.com', 'url' => 'https://example.co.uk', 'result' => false], + + // Escapes + ['config' => 'https://(.*?).com', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://example.com', 'url' => 'https://example.co.uk#https://example.com', 'result' => false], + + // Multi values + ['config' => '*//example.org *//example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => '*//example.org *//example.com', 'url' => 'https://example.com/a/b/c?test=cat#hello', 'result' => true], + ['config' => '*.example.org *.example.com', 'url' => 'https://example.co.uk', 'result' => false], + ['config' => ' *.example.org *.example.com ', 'url' => 'https://example.co.uk', 'result' => false], + ['config' => '* *.example.com', 'url' => 'https://example.co.uk', 'result' => true], + ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.co.uk', 'result' => true], + ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.net', 'result' => false], + ]; + + foreach ($testMap as $test) { + $result = (new SsrUrlValidator($test['config']))->allowed($test['url']); + $this->assertEquals($test['result'], $result, "Failed asserting url '{$test['url']}' with config '{$test['config']}' results " . ($test['result'] ? 'true' : 'false')); + } + } + + public function test_enssure_allowed() + { + $result = (new SsrUrlValidator('https://example.com'))->ensureAllowed('https://example.com'); + $this->assertNull($result); + + $this->expectException(HttpFetchException::class); + (new SsrUrlValidator('https://example.com'))->ensureAllowed('https://test.example.com'); + } +} diff --git a/tests/Uploads/AvatarTest.php b/tests/Uploads/AvatarTest.php index 363c1fa95..f5b49a9fc 100644 --- a/tests/Uploads/AvatarTest.php +++ b/tests/Uploads/AvatarTest.php @@ -3,9 +3,11 @@ namespace Tests\Uploads; use BookStack\Exceptions\HttpFetchException; -use BookStack\Uploads\HttpFetcher; use BookStack\Uploads\UserAvatars; use BookStack\Users\Models\User; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; use Tests\TestCase; class AvatarTest extends TestCase @@ -22,27 +24,16 @@ class AvatarTest extends TestCase return User::query()->where('email', '=', $user->email)->first(); } - protected function assertImageFetchFrom(string $url) - { - $http = $this->mock(HttpFetcher::class); - - $http->shouldReceive('fetch') - ->once()->with($url) - ->andReturn($this->files->pngImageData()); - } - - protected function deleteUserImage(User $user) + protected function deleteUserImage(User $user): void { $this->files->deleteAtRelativePath($user->avatar->path); } public function test_gravatar_fetched_on_user_create() { - config()->set([ - 'services.disable_services' => false, - ]); + $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]); + config()->set(['services.disable_services' => false]); $user = User::factory()->make(); - $this->assertImageFetchFrom('https://www.gravatar.com/avatar/' . md5(strtolower($user->email)) . '?s=500&d=identicon'); $user = $this->createUserRequest($user); $this->assertDatabaseHas('images', [ @@ -50,6 +41,9 @@ class AvatarTest extends TestCase 'created_by' => $user->id, ]); $this->deleteUserImage($user); + + $expectedUri = 'https://www.gravatar.com/avatar/' . md5(strtolower($user->email)) . '?s=500&d=identicon'; + $this->assertEquals($expectedUri, $requests->latestRequest()->getUri()); } public function test_custom_url_used_if_set() @@ -61,24 +55,22 @@ class AvatarTest extends TestCase $user = User::factory()->make(); $url = 'https://example.com/' . urlencode(strtolower($user->email)) . '/' . md5(strtolower($user->email)) . '/500'; - $this->assertImageFetchFrom($url); + $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]); $user = $this->createUserRequest($user); + $this->assertEquals($url, $requests->latestRequest()->getUri()); $this->deleteUserImage($user); } public function test_avatar_not_fetched_if_no_custom_url_and_services_disabled() { - config()->set([ - 'services.disable_services' => true, - ]); - + config()->set(['services.disable_services' => true]); $user = User::factory()->make(); - - $http = $this->mock(HttpFetcher::class); - $http->shouldNotReceive('fetch'); + $requests = $this->mockHttpClient([new Response()]); $this->createUserRequest($user); + + $this->assertEquals(0, $requests->requestCount()); } public function test_avatar_not_fetched_if_avatar_url_option_set_to_false() @@ -89,21 +81,18 @@ class AvatarTest extends TestCase ]); $user = User::factory()->make(); - - $http = $this->mock(HttpFetcher::class); - $http->shouldNotReceive('fetch'); + $requests = $this->mockHttpClient([new Response()]); $this->createUserRequest($user); + + $this->assertEquals(0, $requests->requestCount()); } public function test_no_failure_but_error_logged_on_failed_avatar_fetch() { - config()->set([ - 'services.disable_services' => false, - ]); + config()->set(['services.disable_services' => false]); - $http = $this->mock(HttpFetcher::class); - $http->shouldReceive('fetch')->andThrow(new HttpFetchException()); + $this->mockHttpClient([new ConnectException('Failed to connect', new Request('GET', ''))]); $logger = $this->withTestLogger(); @@ -122,17 +111,16 @@ class AvatarTest extends TestCase $user = User::factory()->make(); $avatar = app()->make(UserAvatars::class); - $url = 'http_malformed_url/' . urlencode(strtolower($user->email)) . '/' . md5(strtolower($user->email)) . '/500'; $logger = $this->withTestLogger(); + $this->mockHttpClient([new ConnectException('Could not resolve host http_malformed_url', new Request('GET', ''))]); $avatar->fetchAndAssignToUser($user); + $url = 'http_malformed_url/' . urlencode(strtolower($user->email)) . '/' . md5(strtolower($user->email)) . '/500'; $this->assertTrue($logger->hasError('Failed to save user avatar image')); $exception = $logger->getRecords()[0]['context']['exception']; - $this->assertEquals(new HttpFetchException( - 'Cannot get image from ' . $url, - 6, - (new HttpFetchException('Could not resolve host: http_malformed_url', 6)) - ), $exception); + $this->assertInstanceOf(HttpFetchException::class, $exception); + $this->assertEquals('Cannot get image from ' . $url, $exception->getMessage()); + $this->assertEquals('Could not resolve host http_malformed_url', $exception->getPrevious()->getMessage()); } } diff --git a/tests/User/UserManagementTest.php b/tests/User/UserManagementTest.php index df60bede6..93d35f5d0 100644 --- a/tests/User/UserManagementTest.php +++ b/tests/User/UserManagementTest.php @@ -191,7 +191,7 @@ class UserManagementTest extends TestCase public function test_guest_profile_shows_limited_form() { - $guest = User::getDefault(); + $guest = $this->users->guest(); $resp = $this->asAdmin()->get('/settings/users/' . $guest->id); $resp->assertSee('Guest'); $this->withHtml($resp)->assertElementNotExists('#password'); @@ -199,7 +199,7 @@ class UserManagementTest extends TestCase public function test_guest_profile_cannot_be_deleted() { - $guestUser = User::getDefault(); + $guestUser = $this->users->guest(); $resp = $this->asAdmin()->get('/settings/users/' . $guestUser->id . '/delete'); $resp->assertSee('Delete User'); $resp->assertSee('Guest'); @@ -215,7 +215,7 @@ class UserManagementTest extends TestCase { $langs = ['en', 'fr', 'hr']; foreach ($langs as $lang) { - config()->set('app.locale', $lang); + config()->set('app.default_locale', $lang); $resp = $this->asAdmin()->get('/settings/users/create'); $this->withHtml($resp)->assertElementExists('select[name="language"] option[value="' . $lang . '"][selected]'); } diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index e47a259a5..4a6cba7b3 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -2,10 +2,30 @@ namespace Tests\User; +use BookStack\Activity\Tools\UserEntityWatchOptions; +use BookStack\Activity\WatchLevels; use Tests\TestCase; class UserPreferencesTest extends TestCase { + public function test_index_view() + { + $resp = $this->asEditor()->get('/preferences'); + $resp->assertOk(); + $resp->assertSee('Interface Keyboard Shortcuts'); + $resp->assertSee('Edit Profile'); + } + + public function test_index_view_accessible_but_without_profile_and_notifications_for_guest_user() + { + $this->setSettings(['app-public' => 'true']); + $this->permissions->grantUserRolePermissions($this->users->guest(), ['receive-notifications']); + $resp = $this->get('/preferences'); + $resp->assertOk(); + $resp->assertSee('Interface Keyboard Shortcuts'); + $resp->assertDontSee('Edit Profile'); + $resp->assertDontSee('Notification'); + } public function test_interface_shortcuts_updating() { $this->asEditor(); @@ -45,6 +65,110 @@ class UserPreferencesTest extends TestCase $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]'); } + public function test_notification_routes_requires_notification_permission() + { + $viewer = $this->users->viewer(); + $resp = $this->actingAs($viewer)->get('/preferences/notifications'); + $this->assertPermissionError($resp); + + $resp = $this->put('/preferences/notifications'); + $this->assertPermissionError($resp); + + $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']); + $resp = $this->get('/preferences/notifications'); + $resp->assertOk(); + $resp->assertSee('Notification Preferences'); + } + + public function test_notification_preferences_updating() + { + $editor = $this->users->editor(); + + // View preferences with defaults + $resp = $this->actingAs($editor)->get('/preferences/notifications'); + $resp->assertSee('Notification Preferences'); + + $html = $this->withHtml($resp); + $html->assertFieldHasValue('preferences[comment-replies]', 'false'); + + // Update preferences + $resp = $this->put('/preferences/notifications', [ + 'preferences' => ['comment-replies' => 'true'], + ]); + + $resp->assertRedirect('/preferences/notifications'); + $resp->assertSessionHas('success', 'Notification preferences have been updated!'); + + // View updates to preferences page + $resp = $this->get('/preferences/notifications'); + $html = $this->withHtml($resp); + $html->assertFieldHasValue('preferences[comment-replies]', 'true'); + } + + public function test_notification_preferences_show_watches() + { + $editor = $this->users->editor(); + $book = $this->entities->book(); + + $options = new UserEntityWatchOptions($editor, $book); + $options->updateLevelByValue(WatchLevels::COMMENTS); + + $resp = $this->actingAs($editor)->get('/preferences/notifications'); + $resp->assertSee($book->name); + $resp->assertSee('All Page Updates & Comments'); + + $options->updateLevelByValue(WatchLevels::DEFAULT); + + $resp = $this->actingAs($editor)->get('/preferences/notifications'); + $resp->assertDontSee($book->name); + $resp->assertDontSee('All Page Updates & Comments'); + } + + public function test_notification_preferences_dont_error_on_deleted_items() + { + $editor = $this->users->editor(); + $book = $this->entities->book(); + + $options = new UserEntityWatchOptions($editor, $book); + $options->updateLevelByValue(WatchLevels::COMMENTS); + + $this->actingAs($editor)->delete($book->getUrl()); + $book->refresh(); + $this->assertNotNull($book->deleted_at); + + $resp = $this->actingAs($editor)->get('/preferences/notifications'); + $resp->assertOk(); + $resp->assertDontSee($book->name); + } + + public function test_notification_preferences_not_accessible_to_guest() + { + $this->setSettings(['app-public' => 'true']); + $guest = $this->users->guest(); + $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']); + + $resp = $this->get('/preferences/notifications'); + $this->assertPermissionError($resp); + + $resp = $this->put('/preferences/notifications', [ + 'preferences' => ['comment-replies' => 'true'], + ]); + $this->assertPermissionError($resp); + } + + public function test_notification_comment_options_only_exist_if_comments_active() + { + $resp = $this->asEditor()->get('/preferences/notifications'); + $resp->assertSee('Notify upon comments'); + $resp->assertSee('Notify upon replies'); + + setting()->put('app-disable-comments', true); + + $resp = $this->get('/preferences/notifications'); + $resp->assertDontSee('Notify upon comments'); + $resp->assertDontSee('Notify upon replies'); + } + public function test_update_sort_preference() { $editor = $this->users->editor(); @@ -131,6 +255,22 @@ class UserPreferencesTest extends TestCase $this->withHtml($home)->assertElementExists('.dark-mode'); } + public function test_dark_mode_toggle_endpoint_changes_to_light_when_dark_by_default() + { + config()->set('setting-defaults.user.dark-mode-enabled', true); + $editor = $this->users->editor(); + + $this->assertEquals(true, setting()->getUser($editor, 'dark-mode-enabled')); + $prefChange = $this->actingAs($editor)->patch('/preferences/toggle-dark-mode'); + $prefChange->assertRedirect(); + $this->assertEquals(false, setting()->getUser($editor, 'dark-mode-enabled')); + + $home = $this->get('/'); + $this->withHtml($home)->assertElementNotExists('.dark-mode'); + $home->assertDontSee('Light Mode'); + $home->assertSee('Dark Mode'); + } + public function test_books_view_type_preferences_when_list() { $editor = $this->users->editor(); diff --git a/tests/User/UserSearchTest.php b/tests/User/UserSearchTest.php index 1387311ce..76efbf4af 100644 --- a/tests/User/UserSearchTest.php +++ b/tests/User/UserSearchTest.php @@ -57,8 +57,7 @@ class UserSearchTest extends TestCase public function test_select_requires_logged_in_user() { $this->setSettings(['app-public' => true]); - $defaultUser = User::getDefault(); - $this->permissions->grantUserRolePermissions($defaultUser, ['users-manage']); + $this->permissions->grantUserRolePermissions($this->users->guest(), ['users-manage']); $resp = $this->get('/search/users/select?search=a'); $this->assertPermissionError($resp); diff --git a/version b/version index e05898580..05edb56cc 100644 --- a/version +++ b/version @@ -1 +1 @@ -v23.06-dev +v23.09-dev
- Stellar Hosted + + Cloudabove Practicali @@ -81,6 +82,7 @@ Details about BookStack's versioning scheme and the general release process [can ## 🌎 Translations Translations for text within BookStack is managed through the [BookStack project on Crowdin](https://crowdin.com/project/bookstack). Some strings have colon-prefixed variables such as `:userName`. Leave these values as they are as they will be replaced at run-time. Crowdin is the preferred way to provide translations, otherwise the raw translations files can be found within the `resources/lang` path. +Translations to original files, provided via pull request, will be fed back through Crowdin instead of being merged directly and therefore your changes & commits will not be reflected in git contribution history. If you'd like a new language to be added to Crowdin, for you to be able to provide translations for, please [open a new issue here](https://github.com/BookStackApp/BookStack/issues/new?template=language_request.yml). @@ -102,7 +104,7 @@ Security information for administering a BookStack instance can be found on the If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](https://updates.bookstackapp.com/signup/bookstack-security-updates). -If you would like to report a security concern, details of doing so can [can be found here](https://github.com/BookStackApp/BookStack/blob/development/.github/SECURITY.md). +If you would like to report a security concern, details of doing so [can be found here](https://github.com/BookStackApp/BookStack/blob/development/.github/SECURITY.md). ## ♿ Accessibility @@ -140,9 +142,12 @@ Note: This is not an exhaustive list of all libraries and projects that would be * [OneLogin's SAML PHP Toolkit](https://github.com/onelogin/php-saml) - _[MIT](https://github.com/onelogin/php-saml/blob/master/LICENSE)_ * [League/CommonMark](https://commonmark.thephpleague.com/) - _[BSD-3-Clause](https://github.com/thephpleague/commonmark/blob/2.2/LICENSE)_ * [League/Flysystem](https://flysystem.thephpleague.com) - _[MIT](https://github.com/thephpleague/flysystem/blob/3.x/LICENSE)_ +* [League/html-to-markdown](https://github.com/thephpleague/html-to-markdown) - _[MIT](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE)_ +* [League/oauth2-client](https://oauth2-client.thephpleague.com/) - _[MIT](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE)_ * [pragmarx/google2fa](https://github.com/antonioribeiro/google2fa) - _[MIT](https://github.com/antonioribeiro/google2fa/blob/8.x/LICENSE.md)_ * [Bacon/BaconQrCode](https://github.com/Bacon/BaconQrCode) - _[BSD-2-Clause](https://github.com/Bacon/BaconQrCode/blob/master/LICENSE)_ * [phpseclib](https://github.com/phpseclib/phpseclib) - _[MIT](https://github.com/phpseclib/phpseclib/blob/master/LICENSE)_ * [Clockwork](https://github.com/itsgoingd/clockwork) - _[MIT](https://github.com/itsgoingd/clockwork/blob/master/LICENSE)_ * [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan) - _[MIT](https://github.com/phpstan/phpstan/blob/master/LICENSE) and [MIT](https://github.com/nunomaduro/larastan/blob/master/LICENSE.md)_ * [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt)_ +* [JakeArchibald/IDB-Keyval](https://github.com/jakearchibald/idb-keyval) - _[Apache-2.0](https://github.com/jakearchibald/idb-keyval/blob/main/LICENCE)_ diff --git a/resources/icons/user-preferences.svg b/resources/icons/user-preferences.svg new file mode 100644 index 000000000..5ae1773ca --- /dev/null +++ b/resources/icons/user-preferences.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/watch-ignore.svg b/resources/icons/watch-ignore.svg new file mode 100644 index 000000000..2c6ffc24a --- /dev/null +++ b/resources/icons/watch-ignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/view.svg b/resources/icons/watch.svg similarity index 85% rename from resources/icons/view.svg rename to resources/icons/watch.svg index c95c8875c..0be661912 100644 --- a/resources/icons/view.svg +++ b/resources/icons/watch.svg @@ -1,4 +1,3 @@ - \ No newline at end of file diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index b68f332b6..2c5919a37 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -132,6 +132,7 @@ export class Dropdown extends Component { onSelect(this.toggle, event => { event.stopPropagation(); + event.preventDefault(); this.show(event); if (event instanceof KeyboardEvent) { keyboardNavHandler.focusNext(); diff --git a/resources/js/components/page-comments.js b/resources/js/components/page-comments.js index a46a5c3b3..e2911afc6 100644 --- a/resources/js/components/page-comments.js +++ b/resources/js/components/page-comments.js @@ -27,16 +27,12 @@ export class PageComments extends Component { // Internal State this.parentId = null; - this.formReplyText = this.formReplyLink.textContent; + this.formReplyText = this.formReplyLink?.textContent || ''; this.setupListeners(); } setupListeners() { - this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this)); - this.hideFormButton.addEventListener('click', this.hideForm.bind(this)); - this.addCommentButton.addEventListener('click', this.showForm.bind(this)); - this.elem.addEventListener('page-comment-delete', () => { this.updateCount(); this.hideForm(); @@ -47,6 +43,9 @@ export class PageComments extends Component { }); if (this.form) { + this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this)); + this.hideFormButton.addEventListener('click', this.hideForm.bind(this)); + this.addCommentButton.addEventListener('click', this.showForm.bind(this)); this.form.addEventListener('submit', this.saveComment.bind(this)); } } @@ -123,9 +122,8 @@ export class PageComments extends Component { this.showForm(); this.parentId = commentLocalId; this.replyToRow.toggleAttribute('hidden', false); - const replyLink = this.replyToRow.querySelector('a'); - replyLink.textContent = this.formReplyText.replace('1234', this.parentId); - replyLink.href = `#comment${this.parentId}`; + this.formReplyLink.textContent = this.formReplyText.replace('1234', this.parentId); + this.formReplyLink.href = `#comment${this.parentId}`; } removeReplyTo() { diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index f66b7921d..3f9df4778 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -82,18 +82,20 @@ export class Actions { const selectionRange = this.#getSelectionRange(); - DrawIO.show(url, () => Promise.resolve(''), pngData => { + DrawIO.show(url, () => Promise.resolve(''), async pngData => { const data = { image: pngData, uploaded_to: Number(this.editor.config.pageId), }; - window.$http.post('/images/drawio', data).then(resp => { + try { + const resp = await window.$http.post('/images/drawio', data); this.#insertDrawing(resp.data, selectionRange); DrawIO.close(); - }).catch(err => { + } catch (err) { this.handleDrawingUploadError(err); - }); + throw new Error(`Failed to save image with error: ${err}`); + } }); } @@ -112,13 +114,14 @@ export class Actions { const selectionRange = this.#getSelectionRange(); const drawingId = imgContainer.getAttribute('drawio-diagram'); - DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), pngData => { + DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), async pngData => { const data = { image: pngData, uploaded_to: Number(this.editor.config.pageId), }; - window.$http.post('/images/drawio', data).then(resp => { + try { + const resp = await window.$http.post('/images/drawio', data); const newText = `
`; const newContent = this.#getText().split('\n').map(line => { if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) { @@ -128,9 +131,10 @@ export class Actions { }).join('\n'); this.#setText(newContent, selectionRange); DrawIO.close(); - }).catch(err => { + } catch (err) { this.handleDrawingUploadError(err); - }); + throw new Error(`Failed to save image with error: ${err}`); + } }); } diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index efc071d3e..46e10327a 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -1,17 +1,22 @@ // Docs: https://www.diagrams.net/doc/faq/embed-mode +import * as store from './store'; let iFrame = null; let lastApprovedOrigin; -let onInit; let - onSave; +let onInit; +let onSave; +const saveBackupKey = 'last-drawing-save'; function drawPostMessage(data) { iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin); } function drawEventExport(message) { + store.set(saveBackupKey, message.data); if (onSave) { - onSave(message.data); + onSave(message.data).then(() => { + store.del(saveBackupKey); + }); } } @@ -63,15 +68,42 @@ function drawReceive(event) { } /** - * Show the draw.io editor. - * @param {String} drawioUrl - * @param {Function} onInitCallback - Must return a promise with the xml to load for the editor. - * @param {Function} onSaveCallback - Is called with the drawing data on save. + * Attempt to prompt and restore unsaved drawing content if existing. + * @returns {Promise} */ -export function show(drawioUrl, onInitCallback, onSaveCallback) { +async function attemptRestoreIfExists() { + const backupVal = await store.get(saveBackupKey); + const dialogEl = document.getElementById('unsaved-drawing-dialog'); + + if (!dialogEl) { + console.error('Missing expected unsaved-drawing dialog'); + } + + if (backupVal) { + /** @var {ConfirmDialog} */ + const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog'); + const restore = await dialog.show(); + if (restore) { + onInit = async () => backupVal; + } + } +} + +/** + * Show the draw.io editor. + * onSaveCallback must return a promise that resolves on successful save and errors on failure. + * onInitCallback must return a promise with the xml to load for the editor. + * Will attempt to provide an option to restore unsaved changes if found to exist. + * @param {String} drawioUrl + * @param {Function>} onInitCallback + * @param {Function} onSaveCallback - Is called with the drawing data on save. + */ +export async function show(drawioUrl, onInitCallback, onSaveCallback) { onInit = onInitCallback; onSave = onSaveCallback; + await attemptRestoreIfExists(); + iFrame = document.createElement('iframe'); iFrame.setAttribute('frameborder', '0'); window.addEventListener('message', drawReceive); diff --git a/resources/js/services/store.js b/resources/js/services/store.js new file mode 100644 index 000000000..a803be284 --- /dev/null +++ b/resources/js/services/store.js @@ -0,0 +1 @@ +export {get, set, del} from 'idb-keyval'; diff --git a/resources/js/services/util.js b/resources/js/services/util.js index dd97d81aa..d9c3bd0e9 100644 --- a/resources/js/services/util.js +++ b/resources/js/services/util.js @@ -5,11 +5,11 @@ * leading edge, instead of the trailing. * @attribution https://davidwalsh.name/javascript-debounce-function * @param {Function} func - * @param {Number} wait + * @param {Number} waitMs * @param {Boolean} immediate * @returns {Function} */ -export function debounce(func, wait, immediate) { +export function debounce(func, waitMs, immediate) { let timeout; return function debouncedWrapper(...args) { const context = this; @@ -19,7 +19,7 @@ export function debounce(func, wait, immediate) { }; const callNow = immediate && !timeout; clearTimeout(timeout); - timeout = setTimeout(later, wait); + timeout = setTimeout(later, waitMs); if (callNow) func.apply(context, args); }; } @@ -70,3 +70,14 @@ export function uniqueId() { const S4 = () => (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); return (`${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`); } + +/** + * Create a promise that resolves after the given time. + * @param {int} timeMs + * @returns {Promise} + */ +export function wait(timeMs) { + return new Promise(res => { + setTimeout(res, timeMs); + }); +} diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 37b810718..d93c9644e 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -4,6 +4,7 @@ import {scrollToQueryString} from './scrolling'; import {listenForDragAndPaste} from './drop-paste-handling'; import {getPrimaryToolbar, registerAdditionalToolbars} from './toolbars'; import {registerCustomIcons} from './icons'; +import {setupFilters} from './filters'; import {getPlugin as getCodeeditorPlugin} from './plugin-codeeditor'; import {getPlugin as getDrawioPlugin} from './plugin-drawio'; @@ -147,23 +148,6 @@ function fetchCustomHeadContent() { return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); } -/** - * Setup a serializer filter for
tags to ensure they're not rendered - * within code blocks and that we use newlines there instead. - * @param {Editor} editor - */ -function setupBrFilter(editor) { - editor.serializer.addNodeFilter('br', nodes => { - for (const node of nodes) { - if (node.parent && node.parent.name === 'code') { - const newline = window.tinymce.html.Node.create('#text'); - newline.value = '\n'; - node.replace(newline); - } - } - }); -} - /** * @param {WysiwygConfigOptions} options * @return {function(Editor)} @@ -189,7 +173,7 @@ function getSetupCallback(options) { }); editor.on('PreInit', () => { - setupBrFilter(editor); + setupFilters(editor); }); // Custom handler hook diff --git a/resources/js/wysiwyg/filters.js b/resources/js/wysiwyg/filters.js new file mode 100644 index 000000000..dee5df4c2 --- /dev/null +++ b/resources/js/wysiwyg/filters.js @@ -0,0 +1,43 @@ +/** + * Setup a serializer filter for
tags to ensure they're not rendered + * within code blocks and that we use newlines there instead. + * @param {Editor} editor + */ +function setupBrFilter(editor) { + editor.serializer.addNodeFilter('br', nodes => { + for (const node of nodes) { + if (node.parent && node.parent.name === 'code') { + const newline = window.tinymce.html.Node.create('#text'); + newline.value = '\n'; + node.replace(newline); + } + } + }); +} + +/** + * Remove accidentally added pointer elements that are within the content. + * These could have accidentally been added via getting caught in range + * selection within page content. + * @param {Editor} editor + */ +function setupPointerFilter(editor) { + editor.parser.addNodeFilter('div', nodes => { + for (const node of nodes) { + const id = node.attr('id') || ''; + const nodeClass = node.attr('class') || ''; + if (id === 'pointer' || nodeClass.includes('pointer')) { + node.remove(); + } + } + }); +} + +/** + * Setup global default filters for the given editor instance. + * @param {Editor} editor + */ +export function setupFilters(editor) { + setupBrFilter(editor); + setupPointerFilter(editor); +} diff --git a/resources/js/wysiwyg/plugin-drawio.js b/resources/js/wysiwyg/plugin-drawio.js index 7b1750786..3b343a958 100644 --- a/resources/js/wysiwyg/plugin-drawio.js +++ b/resources/js/wysiwyg/plugin-drawio.js @@ -1,4 +1,5 @@ import * as DrawIO from '../services/drawio'; +import {wait} from '../services/util'; let pageEditor = null; let currentNode = null; @@ -33,7 +34,6 @@ function showDrawingManager(mceEditor, selectedNode = null) { } async function updateContent(pngData) { - const id = `image-${Math.random().toString(16).slice(2)}`; const loadingImage = window.baseUrl('/loading.gif'); const handleUploadError = error => { @@ -57,24 +57,29 @@ async function updateContent(pngData) { }); } catch (err) { handleUploadError(err); + throw new Error(`Failed to save image with error: ${err}`); } return; } - setTimeout(async () => { - pageEditor.insertContent(`
`); - DrawIO.close(); - try { - const img = await DrawIO.upload(pngData, options.pageId); - pageEditor.undoManager.transact(() => { - pageEditor.dom.setAttrib(id, 'src', img.url); - pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id); - }); - } catch (err) { - pageEditor.dom.remove(id); - handleUploadError(err); - } - }, 5); + await wait(5); + + const id = `drawing-${Math.random().toString(16).slice(2)}`; + const wrapId = `drawing-wrap-${Math.random().toString(16).slice(2)}`; + pageEditor.insertContent(`
`); + DrawIO.close(); + + try { + const img = await DrawIO.upload(pngData, options.pageId); + pageEditor.undoManager.transact(() => { + pageEditor.dom.setAttrib(id, 'src', img.url); + pageEditor.dom.setAttrib(wrapId, 'drawio-diagram', img.id); + }); + } catch (err) { + pageEditor.dom.remove(wrapId); + handleUploadError(err); + throw new Error(`Failed to save image with error: ${err}`); + } } function drawingInit() { diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 4722d9aa1..2e534d60f 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -105,6 +105,7 @@ max-width: 100%; flex-grow: 1; flex-basis: auto !important; + min-height: 0; } .editor-toolbar-label { float: none !important; @@ -119,7 +120,6 @@ #markdown-editor .markdown-editor-wrap:not(.active) { flex-grow: 0; flex: none; - min-height: 0; } } diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index a8604b81b..503298fcc 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -304,7 +304,7 @@ body.flexbox { .screen-reader-only { position: absolute; - left: -10000px; + inset-inline-start: -10000px; top: auto; width: 1px; height: 1px; @@ -353,7 +353,7 @@ body.flexbox { margin-inline-end: $-xl; grid-template-columns: 1fr 4fr 1fr; grid-template-areas: "a b c"; - grid-column-gap: $-xxl; + grid-column-gap: $-xl; .tri-layout-right { grid-area: c; min-width: 0; @@ -378,6 +378,14 @@ body.flexbox { padding-inline-end: $-l; } } +@include between($xxl, $xxxl) { + .tri-layout-container { + grid-template-columns: 1fr calc(940px + (2 * $-m)) 1fr; + grid-column-gap: $-s; + margin-inline-start: $-m; + margin-inline-end: $-m; + } +} @include between($l, $xxl) { .tri-layout-left { position: sticky; diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index ad0803e71..323551196 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -672,7 +672,7 @@ ul.pagination { @include lightDark(color, #555, #eee); fill: currentColor; text-align: start !important; - max-height: 500px; + max-height: 80vh; overflow-y: auto; &.anchor-left { inset-inline-end: auto; @@ -681,6 +681,10 @@ ul.pagination { &.wide { min-width: 220px; } + &.xl-limited { + width: 280px; + max-width: 100%; + } .text-muted { color: #999; fill: #999; @@ -705,6 +709,11 @@ ul.pagination { white-space: nowrap; line-height: 1.4; cursor: pointer; + &.break-text { + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + } &:hover, &:focus { text-decoration: none; background-color: var(--color-primary-light); diff --git a/resources/sass/_text.scss b/resources/sass/_text.scss index 7cade9607..a3e6f09ac 100644 --- a/resources/sass/_text.scss +++ b/resources/sass/_text.scss @@ -365,6 +365,7 @@ li.checkbox-item, li.task-list-item { } .break-text { + white-space: normal; word-wrap: break-word; overflow-wrap: break-word; } diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss index a3598e29c..35586bf58 100644 --- a/resources/sass/_variables.scss +++ b/resources/sass/_variables.scss @@ -2,6 +2,7 @@ /////////////// // Screen breakpoints +$xxxl: 1700px; $xxl: 1400px; $xl: 1100px; $l: 1000px; diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 8bb41c18b..9e7df4156 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -70,7 +70,7 @@
{{ trans('common.details') }}

- {{ trans('common.email_action_help', ['actionText' => $actionText]) }} + {{ $locale->trans('common.email_action_help', ['actionText' => $actionText]) }}

@@ -187,7 +187,7 @@ $style = [

© {{ date('Y') }} {{ setting('app-name') }}. - {{ trans('common.email_rights') }} + {{ $locale->trans('common.email_rights') }}