mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-09-07 18:07:29 +03:00
Merge pull request #5790 from BookStackApp/timezones
Timezones: Seperate display timezone and consistency update
This commit is contained in:
@@ -36,10 +36,14 @@ APP_LANG=en
|
||||
# APP_LANG will be used if such a header is not provided.
|
||||
APP_AUTO_LANG_PUBLIC=true
|
||||
|
||||
# Application timezone
|
||||
# Used where dates are displayed such as on exported content.
|
||||
# Application timezones
|
||||
# The first option is used to determine what timezone is used for date storage.
|
||||
# Leaving that as "UTC" is advised.
|
||||
# The second option is used to set the timezone which will be used for date
|
||||
# formatting and display. This defaults to the "APP_TIMEZONE" value.
|
||||
# Valid timezone values can be found here: https://www.php.net/manual/en/timezones.php
|
||||
APP_TIMEZONE=UTC
|
||||
APP_DISPLAY_TIMEZONE=UTC
|
||||
|
||||
# Application theme
|
||||
# Used to specific a themes/<APP_THEME> folder where BookStack UI
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace BookStack\App\Providers;
|
||||
|
||||
use BookStack\Entities\BreadcrumbsViewComposer;
|
||||
use BookStack\Util\DateFormatter;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\View;
|
||||
@@ -10,6 +11,15 @@ use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ViewTweaksServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(DateFormatter::class, function ($app) {
|
||||
return new DateFormatter(
|
||||
$app['config']->get('app.display_timezone'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
@@ -21,6 +31,9 @@ class ViewTweaksServiceProvider extends ServiceProvider
|
||||
// View Composers
|
||||
View::composer('entities.breadcrumbs', BreadcrumbsViewComposer::class);
|
||||
|
||||
// View Globals
|
||||
View::share('dates', $this->app->make(DateFormatter::class));
|
||||
|
||||
// Custom blade view directives
|
||||
Blade::directive('icon', function ($expression) {
|
||||
return "<?php echo (new \BookStack\Util\SvgIcon($expression))->toHtml(); ?>";
|
||||
|
@@ -70,8 +70,8 @@ return [
|
||||
// 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.
|
||||
// or user avatars, for example, won't use this list.
|
||||
// Space separated 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', '*'),
|
||||
@@ -80,8 +80,10 @@ return [
|
||||
// Integer value between 0 (IP hidden) to 4 (Full IP usage)
|
||||
'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4),
|
||||
|
||||
// Application timezone for back-end date functions.
|
||||
// Application timezone for stored date/time values.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
// Application timezone for displayed date/time values in the UI.
|
||||
'display_timezone' => env('APP_DISPLAY_TIMEZONE', env('APP_TIMEZONE', 'UTC')),
|
||||
|
||||
// Default locale to use
|
||||
// A default variant is also stored since Laravel can overwrite
|
||||
|
@@ -4,19 +4,15 @@ namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use BookStack\Util\DateFormatter;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PageEditActivity
|
||||
{
|
||||
protected Page $page;
|
||||
|
||||
/**
|
||||
* PageEditActivity constructor.
|
||||
*/
|
||||
public function __construct(Page $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
public function __construct(
|
||||
protected Page $page
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,11 +46,9 @@ class PageEditActivity
|
||||
/**
|
||||
* Get any editor clash warning messages to show for the given draft revision.
|
||||
*
|
||||
* @param PageRevision|Page $draft
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getWarningMessagesForDraft($draft): array
|
||||
public function getWarningMessagesForDraft(Page|PageRevision $draft): array
|
||||
{
|
||||
$warnings = [];
|
||||
|
||||
@@ -82,7 +76,8 @@ class PageEditActivity
|
||||
*/
|
||||
public function getEditingActiveDraftMessage(PageRevision $draft): string
|
||||
{
|
||||
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
|
||||
$formatter = resolve(DateFormatter::class);
|
||||
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $formatter->relative($draft->updated_at)]);
|
||||
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
|
||||
return $message;
|
||||
}
|
||||
|
26
app/Util/DateFormatter.php
Normal file
26
app/Util/DateFormatter.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Util;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
|
||||
class DateFormatter
|
||||
{
|
||||
public function __construct(
|
||||
protected string $displayTimezone,
|
||||
) {
|
||||
}
|
||||
|
||||
public function absolute(Carbon $date): string
|
||||
{
|
||||
$withDisplayTimezone = $date->clone()->setTimezone($this->displayTimezone);
|
||||
|
||||
return $withDisplayTimezone->format('Y-m-d H:i:s T');
|
||||
}
|
||||
|
||||
public function relative(Carbon $date, bool $includeSuffix = true): string
|
||||
{
|
||||
return $date->diffForHumans(null, $includeSuffix ? null : CarbonInterface::DIFF_ABSOLUTE);
|
||||
}
|
||||
}
|
@@ -16,6 +16,8 @@
|
||||
<server name="APP_THEME" value="none"/>
|
||||
<server name="APP_AUTO_LANG_PUBLIC" value="true"/>
|
||||
<server name="APP_URL" value="http://bookstack.dev"/>
|
||||
<server name="APP_TIMEZONE" value="UTC"/>
|
||||
<server name="APP_DISPLAY_TIMEZONE" value="UTC"/>
|
||||
<server name="ALLOWED_IFRAME_HOSTS" value=""/>
|
||||
<server name="ALLOWED_IFRAME_SOURCES" value="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"/>
|
||||
<server name="ALLOWED_SSR_HOSTS" value="*"/>
|
||||
|
@@ -14,7 +14,8 @@
|
||||
<div class="flex-container-row wrap items-center gap-x-xs">
|
||||
@if ($comment->createdBy)
|
||||
<div>
|
||||
<img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar block mr-xs" alt="{{ $comment->createdBy->name }}">
|
||||
<img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar block mr-xs"
|
||||
alt="{{ $comment->createdBy->name }}">
|
||||
</div>
|
||||
@endif
|
||||
<div class="meta text-muted flex-container-row wrap items-center flex text-small">
|
||||
@@ -23,50 +24,55 @@
|
||||
@else
|
||||
{{ trans('common.deleted_user') }}
|
||||
@endif
|
||||
<span title="{{ $comment->created_at }}"> {{ trans('entities.comment_created', ['createDiff' => $comment->created_at->diffForHumans() ]) }}</span>
|
||||
<span title="{{ $dates->absolute($comment->created_at) }}"> {{ trans('entities.comment_created', ['createDiff' => $dates->relative($comment->created_at) ]) }}</span>
|
||||
@if($comment->isUpdated())
|
||||
<span class="mx-xs">•</span>
|
||||
<span title="{{ trans('entities.comment_updated', ['updateDiff' => $comment->updated_at, 'username' => $comment->updatedBy->name ?? trans('common.deleted_user')]) }}">
|
||||
<span title="{{ trans('entities.comment_updated', ['updateDiff' => $dates->absolute($comment->updated_at), 'username' => $comment->updatedBy->name ?? trans('common.deleted_user')]) }}">
|
||||
{{ trans('entities.comment_updated_indicator') }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="right-meta flex-container-row justify-flex-end items-center px-s">
|
||||
@if(!$readOnly && (userCan('comment-create-all') || userCan('comment-update', $comment) || userCan('comment-delete', $comment)))
|
||||
<div class="actions mr-s">
|
||||
@if(userCan('comment-create-all'))
|
||||
<button refs="page-comment@reply-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('reply') {{ trans('common.reply') }}</button>
|
||||
@endif
|
||||
@if(!$comment->parent_id && (userCan('comment-update', $comment) || userCan('comment-delete', $comment)))
|
||||
<button refs="page-comment@archive-button"
|
||||
type="button"
|
||||
data-is-archived="{{ $comment->archived ? 'true' : 'false' }}"
|
||||
class="text-button text-muted hover-underline text-small p-xs">@icon('archive') {{ trans('common.' . ($comment->archived ? 'unarchive' : 'archive')) }}</button>
|
||||
@endif
|
||||
@if(userCan('comment-update', $comment))
|
||||
<button refs="page-comment@edit-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('edit') {{ trans('common.edit') }}</button>
|
||||
@endif
|
||||
@if(userCan('comment-delete', $comment))
|
||||
<div component="dropdown" class="dropdown-container">
|
||||
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" class="text-button text-muted hover-underline text-small p-xs">@icon('delete') {{ trans('common.delete') }}</button>
|
||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||
<li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
|
||||
<li>
|
||||
<button refs="page-comment@delete-button" type="button" class="text-button text-neg icon-item">
|
||||
@icon('delete')
|
||||
<div>{{ trans('common.delete') }}</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<span class="text-muted">
|
||||
<div class="actions mr-s">
|
||||
@if(userCan('comment-create-all'))
|
||||
<button refs="page-comment@reply-button" type="button"
|
||||
class="text-button text-muted hover-underline text-small p-xs">@icon('reply') {{ trans('common.reply') }}</button>
|
||||
@endif
|
||||
@if(!$comment->parent_id && (userCan('comment-update', $comment) || userCan('comment-delete', $comment)))
|
||||
<button refs="page-comment@archive-button"
|
||||
type="button"
|
||||
data-is-archived="{{ $comment->archived ? 'true' : 'false' }}"
|
||||
class="text-button text-muted hover-underline text-small p-xs">@icon('archive') {{ trans('common.' . ($comment->archived ? 'unarchive' : 'archive')) }}</button>
|
||||
@endif
|
||||
@if(userCan('comment-update', $comment))
|
||||
<button refs="page-comment@edit-button" type="button"
|
||||
class="text-button text-muted hover-underline text-small p-xs">@icon('edit') {{ trans('common.edit') }}</button>
|
||||
@endif
|
||||
@if(userCan('comment-delete', $comment))
|
||||
<div component="dropdown" class="dropdown-container">
|
||||
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false"
|
||||
class="text-button text-muted hover-underline text-small p-xs">@icon('delete') {{ trans('common.delete') }}</button>
|
||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||
<li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
|
||||
<li>
|
||||
<button refs="page-comment@delete-button" type="button"
|
||||
class="text-button text-neg icon-item">
|
||||
@icon('delete')
|
||||
<div>{{ trans('common.delete') }}</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<span class="text-muted">
|
||||
•
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<a class="bold text-muted text-small" href="#comment{{$comment->local_id}}">#{{$comment->local_id}}</a>
|
||||
<a class="bold text-muted text-small"
|
||||
href="#comment{{$comment->local_id}}">#{{$comment->local_id}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +82,8 @@
|
||||
<div refs="page-comment@content-container" class="content">
|
||||
@if ($comment->parent_id)
|
||||
<p class="comment-reply">
|
||||
<a class="text-muted text-small" href="#comment{{ $comment->parent_id }}">@icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}</a>
|
||||
<a class="text-muted text-small"
|
||||
href="#comment{{ $comment->parent_id }}">@icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}</a>
|
||||
</p>
|
||||
@endif
|
||||
@if($comment->content_ref)
|
||||
@@ -86,7 +93,8 @@
|
||||
option:page-comment-reference:view-comment-text="{{ trans('entities.comment_view') }}"
|
||||
option:page-comment-reference:jump-to-thread-text="{{ trans('entities.comment_jump_to_thread') }}"
|
||||
option:page-comment-reference:close-text="{{ trans('common.close') }}"
|
||||
href="#">@icon('bookmark'){{ trans('entities.comment_reference') }} <span>{{ trans('entities.comment_reference_outdated') }}</span></a>
|
||||
href="#">@icon('bookmark'){{ trans('entities.comment_reference') }}
|
||||
<span>{{ trans('entities.comment_reference_outdated') }}</span></a>
|
||||
</div>
|
||||
@endif
|
||||
{!! $commentHtml !!}
|
||||
@@ -95,10 +103,12 @@
|
||||
@if(!$readOnly && userCan('comment-update', $comment))
|
||||
<form novalidate refs="page-comment@form" hidden class="content pt-s px-s block">
|
||||
<div class="form-group description-input">
|
||||
<textarea refs="page-comment@input" name="html" rows="3" placeholder="{{ trans('entities.comment_placeholder') }}">{{ $commentHtml }}</textarea>
|
||||
<textarea refs="page-comment@input" name="html" rows="3"
|
||||
placeholder="{{ trans('entities.comment_placeholder') }}">{{ $commentHtml }}</textarea>
|
||||
</div>
|
||||
<div class="form-group text-right">
|
||||
<button type="button" class="button outline" refs="page-comment@form-cancel">{{ trans('common.cancel') }}</button>
|
||||
<button type="button" class="button outline"
|
||||
refs="page-comment@form-cancel">{{ trans('common.cancel') }}</button>
|
||||
<button type="submit" class="button">{{ trans('entities.comment_save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -26,5 +26,5 @@
|
||||
|
||||
<br>
|
||||
|
||||
<span class="text-muted"><small>@icon('time'){{ $activity->created_at->diffForHumans() }}</small></span>
|
||||
<span class="text-muted" title="{{ $dates->absolute($activity->created_at) }}"><small>@icon('time'){{ $dates->relative($activity->created_at) }}</small></span>
|
||||
</div>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<p class="text-muted">{{ $entity->getExcerpt(130) }}</p>
|
||||
</div>
|
||||
<div class="grid-card-footer text-muted ">
|
||||
<p>@icon('star')<span title="{{ $entity->created_at->toDayDateTimeString() }}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span></p>
|
||||
<p>@icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span></p>
|
||||
<p>@icon('star')<span title="{{ $dates->absolute($entity->created_at) }}">{{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }}</span></p>
|
||||
<p>@icon('edit')<span title="{{ $dates->absolute($entity->updated_at) }}">{{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }}</span></p>
|
||||
</div>
|
||||
</a>
|
@@ -27,9 +27,9 @@
|
||||
@endif
|
||||
|
||||
@if(($showUpdatedBy ?? false) && $entity->relationLoaded('updatedBy') && $entity->updatedBy)
|
||||
<small title="{{ $entity->updated_at->toDayDateTimeString() }}">
|
||||
<small title="{{ $dates->absolute($entity->updated_at) }}">
|
||||
{!! trans('entities.meta_updated_name', [
|
||||
'timeLength' => $entity->updated_at->diffForHumans(),
|
||||
'timeLength' => $dates->relative($entity->updated_at),
|
||||
'user' => e($entity->updatedBy->name)
|
||||
]) !!}
|
||||
</small>
|
||||
|
@@ -31,7 +31,7 @@
|
||||
@icon('star')
|
||||
<div>
|
||||
{!! trans('entities.meta_created_name', [
|
||||
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
|
||||
'timeLength' => '<span title="'. $dates->absolute($entity->created_at) .'">'. $dates->relative($entity->created_at) . '</span>',
|
||||
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
|
||||
]) !!}
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
@else
|
||||
<div class="entity-meta-item">
|
||||
@icon('star')
|
||||
<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
|
||||
<span title="{{ $dates->absolute($entity->created_at) }}">{{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
@icon('edit')
|
||||
<div>
|
||||
{!! trans('entities.meta_updated_name', [
|
||||
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
|
||||
'timeLength' => '<span title="' . $dates->absolute($entity->updated_at) .'">' . $dates->relative($entity->updated_at) .'</span>',
|
||||
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
|
||||
]) !!}
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
@elseif (!$entity->isA('revision'))
|
||||
<div class="entity-meta-item">
|
||||
@icon('edit')
|
||||
<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
|
||||
<span title="{{ $dates->absolute($entity->updated_at) }}">{{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="text-right text-muted">
|
||||
<div>{{ trans('entities.import_size', ['size' => $import->getSizeString()]) }}</div>
|
||||
<div><span title="{{ $import->created_at->toISOString() }}">{{ trans('entities.import_uploaded_at', ['relativeTime' => $import->created_at->diffForHumans()]) }}</span></div>
|
||||
<div><span title="{{ $dates->absolute($import->created_at) }}">{{ trans('entities.import_uploaded_at', ['relativeTime' => $dates->relative($import->created_at)]) }}</span></div>
|
||||
@if($import->createdBy)
|
||||
<div>
|
||||
{{ trans('entities.import_uploaded_by') }}
|
||||
|
@@ -5,6 +5,6 @@
|
||||
</div>
|
||||
<div class="px-m py-s flex-container-row gap-m items-center">
|
||||
<div class="bold opacity-80 text-muted">{{ $import->getSizeString() }}</div>
|
||||
<div class="bold opacity-80 text-muted min-width-xs text-right" title="{{ $import->created_at->toISOString() }}">@icon('time'){{ $import->created_at->diffForHumans() }}</div>
|
||||
<div class="bold opacity-80 text-muted min-width-xs text-right" title="{{ $dates->absolute($import->created_at) }}">@icon('time'){{ $dates->relative($import->created_at) }}</div>
|
||||
</div>
|
||||
</div>
|
@@ -4,13 +4,13 @@
|
||||
@endif
|
||||
|
||||
@icon('star'){!! trans('entities.meta_created' . ($entity->createdBy ? '_name' : ''), [
|
||||
'timeLength' => $entity->created_at->isoFormat('D MMMM Y HH:mm:ss'),
|
||||
'timeLength' => $dates->absolute($entity->created_at),
|
||||
'user' => e($entity->createdBy->name ?? ''),
|
||||
]) !!}
|
||||
<br>
|
||||
|
||||
@icon('edit'){!! trans('entities.meta_updated' . ($entity->updatedBy ? '_name' : ''), [
|
||||
'timeLength' => $entity->updated_at->isoFormat('D MMMM Y HH:mm:ss'),
|
||||
'timeLength' => $dates->absolute($entity->updated_at),
|
||||
'user' => e($entity->updatedBy->name ?? '')
|
||||
]) !!}
|
||||
</div>
|
@@ -94,12 +94,12 @@
|
||||
|
||||
<div class="text-muted text-small">
|
||||
<hr class="my-m">
|
||||
<div title="{{ $image->created_at->format('Y-m-d H:i:s') }}">
|
||||
@icon('star') {{ trans('components.image_uploaded', ['uploadedDate' => $image->created_at->diffForHumans()]) }}
|
||||
<div title="{{ $dates->absolute($image->created_at) }}">
|
||||
@icon('star') {{ trans('components.image_uploaded', ['uploadedDate' => $dates->relative($image->created_at)]) }}
|
||||
</div>
|
||||
@if($image->created_at->valueOf() !== $image->updated_at->valueOf())
|
||||
<div title="{{ $image->updated_at->format('Y-m-d H:i:s') }}">
|
||||
@icon('edit') {{ trans('components.image_updated', ['updateDate' => $image->updated_at->diffForHumans()]) }}
|
||||
<div title="{{ $dates->absolute($image->updated_at) }}">
|
||||
@icon('edit') {{ trans('components.image_updated', ['updateDate' => $dates->relative($image->updated_at)]) }}
|
||||
</div>
|
||||
@endif
|
||||
@if($image->createdBy)
|
||||
|
@@ -17,8 +17,8 @@
|
||||
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
|
||||
<br>
|
||||
<div class="text-muted">
|
||||
<small>{{ $revision->created_at->isoFormat('D MMMM Y HH:mm:ss') }}</small>
|
||||
<small>({{ $revision->created_at->diffForHumans() }})</small>
|
||||
<small>{{ $dates->absolute($revision->created_at) }}</small>
|
||||
<small>({{ $dates->relative($revision->created_at) }})</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
draggable="true" template-id="{{ $template->id }}">
|
||||
<div class="template-item-content" title="{{ trans('entities.templates_replace_content') }}">
|
||||
<div>{{ $template->name }}</div>
|
||||
<div class="text-muted">{{ trans('entities.meta_updated', ['timeLength' => $template->updated_at->diffForHumans()]) }}</div>
|
||||
<div class="text-muted" title="{{ $dates->absolute($template->updated_at) }}">{{ trans('entities.meta_updated', ['timeLength' => $dates->relative($template->updated_at)]) }}</div>
|
||||
</div>
|
||||
<div class="template-item-actions">
|
||||
<button type="button"
|
||||
|
@@ -14,9 +14,17 @@
|
||||
<div>
|
||||
<label class="setting-list-label">{{ trans('settings.webhooks_status') }}</label>
|
||||
<p class="mb-none">
|
||||
{{ trans('settings.webhooks_last_called') }} {{ $webhook->last_called_at ? $webhook->last_called_at->diffForHumans() : trans('common.never') }}
|
||||
@if($webhook->last_called_at)
|
||||
<span title="{{ $dates->absolute($webhook->last_called_at) }}">{{ trans('settings.webhooks_last_called') }} {{ $dates->relative($webhook->last_called_at) }}</span>
|
||||
@else
|
||||
<span>{{ trans('settings.webhooks_last_called') }} {{ trans('common.never') }}</span>
|
||||
@endif
|
||||
<br>
|
||||
{{ trans('settings.webhooks_last_errored') }} {{ $webhook->last_errored_at ? $webhook->last_errored_at->diffForHumans() : trans('common.never') }}
|
||||
@if($webhook->last_errored_at)
|
||||
<span title="{{ $dates->absolute($webhook->last_errored_at) }}">{{ trans('settings.webhooks_last_errored') }} {{ $dates->relative($webhook->last_errored_at) }}</span>
|
||||
@else
|
||||
<span>{{ trans('settings.webhooks_last_errored') }} {{ trans('common.never') }}</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
|
@@ -42,12 +42,12 @@
|
||||
<div class="grid half gap-xl v-center">
|
||||
|
||||
<div class="text-muted text-small">
|
||||
<span title="{{ $token->created_at }}">
|
||||
{{ trans('settings.user_api_token_created', ['timeAgo' => $token->created_at->diffForHumans()]) }}
|
||||
<span title="{{ $dates->absolute($token->created_at) }}">
|
||||
{{ trans('settings.user_api_token_created', ['timeAgo' => $dates->relative($token->created_at)]) }}
|
||||
</span>
|
||||
<br>
|
||||
<span title="{{ $token->updated_at }}">
|
||||
{{ trans('settings.user_api_token_updated', ['timeAgo' => $token->created_at->diffForHumans()]) }}
|
||||
<span title="{{ $dates->absolute($token->updated_at) }}">
|
||||
{{ trans('settings.user_api_token_updated', ['timeAgo' => $dates->relative($token->created_at)]) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
@if($user->last_activity_at)
|
||||
<small>{{ trans('settings.users_latest_activity') }}</small>
|
||||
<br>
|
||||
<small title="{{ $user->last_activity_at->format('Y-m-d H:i:s') }}">{{ $user->last_activity_at->diffForHumans() }}</small>
|
||||
<small title="{{ $dates->absolute($user->last_activity_at) }}">{{ $dates->relative($user->last_activity_at) }}</small>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<div>
|
||||
<h4 class="mt-md">{{ $user->name }}</h4>
|
||||
<p class="text-muted">
|
||||
{{ trans('entities.profile_user_for_x', ['time' => $user->created_at->diffForHumans(null, true)]) }}
|
||||
{{ trans('entities.profile_user_for_x', ['time' => $dates->relative($user->created_at, false)]) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -99,9 +99,9 @@ class HtmlExportTest extends TestCase
|
||||
$page = $this->entities->page();
|
||||
|
||||
$resp = $this->asEditor()->get($page->getUrl('/export/html'));
|
||||
$resp->assertSee($page->created_at->isoFormat('D MMMM Y HH:mm:ss'));
|
||||
$resp->assertSee($page->created_at->format('Y-m-d H:i:s T'));
|
||||
$resp->assertDontSee($page->created_at->diffForHumans());
|
||||
$resp->assertSee($page->updated_at->isoFormat('D MMMM Y HH:mm:ss'));
|
||||
$resp->assertSee($page->updated_at->format('Y-m-d H:i:s T'));
|
||||
$resp->assertDontSee($page->updated_at->diffForHumans());
|
||||
}
|
||||
|
||||
|
37
tests/Util/DateFormatterTest.php
Normal file
37
tests/Util/DateFormatterTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Util;
|
||||
|
||||
use BookStack\Util\DateFormatter;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DateFormatterTest extends TestCase
|
||||
{
|
||||
public function test_iso_with_timezone_alters_from_stored_to_display_timezone()
|
||||
{
|
||||
$formatter = new DateFormatter('Europe/London');
|
||||
$dateTime = new Carbon('2020-06-01 12:00:00', 'UTC');
|
||||
|
||||
$result = $formatter->absolute($dateTime);
|
||||
$this->assertEquals('2020-06-01 13:00:00 BST', $result);
|
||||
}
|
||||
|
||||
public function test_iso_with_timezone_works_from_non_utc_dates()
|
||||
{
|
||||
$formatter = new DateFormatter('Asia/Shanghai');
|
||||
$dateTime = new Carbon('2025-06-10 15:25:00', 'America/New_York');
|
||||
|
||||
$result = $formatter->absolute($dateTime);
|
||||
$this->assertEquals('2025-06-11 03:25:00 CST', $result);
|
||||
}
|
||||
|
||||
public function test_relative()
|
||||
{
|
||||
$formatter = new DateFormatter('Europe/London');
|
||||
$dateTime = (new Carbon('now', 'UTC'))->subMinutes(50);
|
||||
|
||||
$result = $formatter->relative($dateTime);
|
||||
$this->assertEquals('50 minutes ago', $result);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user