Merge branch 'master' into release

This commit is contained in:
Dan Brown 2021-01-10 23:29:11 +00:00
commit 611d37da04
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
14 changed files with 124 additions and 59 deletions

View File

@ -1,19 +1,20 @@
<?php namespace BookStack\Auth; <?php namespace BookStack\Auth;
use BookStack\Actions\Activity;
use BookStack\Api\ApiToken; use BookStack\Api\ApiToken;
use BookStack\Interfaces\Loggable; use BookStack\Interfaces\Loggable;
use BookStack\Model; use BookStack\Model;
use BookStack\Notifications\ResetPassword; use BookStack\Notifications\ResetPassword;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Carbon\Carbon; use Carbon\Carbon;
use Exception;
use Illuminate\Auth\Authenticatable; use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -46,6 +47,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/ */
protected $fillable = ['name', 'email']; protected $fillable = ['name', 'email'];
protected $casts = ['last_activity_at' => 'datetime'];
/** /**
* The attributes excluded from the model's JSON form. * The attributes excluded from the model's JSON form.
* @var array * @var array
@ -181,7 +184,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* Get the social account associated with this user. * Get the social account associated with this user.
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return HasMany
*/ */
public function socialAccounts() public function socialAccounts()
{ {
@ -218,7 +221,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
try { try {
$avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default; $avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default;
} catch (\Exception $err) { } catch (Exception $err) {
$avatar = $default; $avatar = $default;
} }
return $avatar; return $avatar;
@ -226,7 +229,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* Get the avatar for the user. * Get the avatar for the user.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return BelongsTo
*/ */
public function avatar() public function avatar()
{ {
@ -242,11 +245,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
} }
/** /**
* Get the latest activity instance for this user. * Get the last activity time for this user.
*/ */
public function latestActivity(): HasOne public function scopeWithLastActivityAt(Builder $query)
{ {
return $this->hasOne(Activity::class)->latest(); $query->addSelect(['activities.created_at as last_activity_at'])
->leftJoinSub(function (\Illuminate\Database\Query\Builder $query) {
$query->from('activities')->select('user_id')
->selectRaw('max(created_at) as created_at')
->groupBy('user_id');
}, 'activities', 'users.id', '=', 'activities.user_id');
} }
/** /**

View File

@ -59,14 +59,10 @@ class UserRepo
public function getAllUsersPaginatedAndSorted(int $count, array $sortData): LengthAwarePaginator public function getAllUsersPaginatedAndSorted(int $count, array $sortData): LengthAwarePaginator
{ {
$sort = $sortData['sort']; $sort = $sortData['sort'];
if ($sort === 'latest_activity') {
$sort = \BookStack\Actions\Activity::query()->select('created_at')
->whereColumn('activities.user_id', 'users.id')
->latest()
->take(1);
}
$query = User::query()->with(['roles', 'avatar', 'latestActivity']) $query = User::query()->select(['*'])
->withLastActivityAt()
->with(['roles', 'avatar'])
->orderBy($sort, $sortData['order']); ->orderBy($sort, $sortData['order']);
if ($sortData['search']) { if ($sortData['search']) {

View File

@ -35,7 +35,7 @@ class BookRepo
*/ */
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
{ {
return Book::visible()->orderBy($sort, $order)->paginate($count); return Book::visible()->with('cover')->orderBy($sort, $order)->paginate($count);
} }
/** /**

View File

@ -30,7 +30,7 @@ class BookshelfRepo
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
{ {
return Bookshelf::visible() return Bookshelf::visible()
->with('visibleBooks') ->with(['visibleBooks', 'cover'])
->orderBy($sort, $order) ->orderBy($sort, $order)
->paginate($count); ->paginate($count);
} }

View File

@ -0,0 +1,16 @@
<?php namespace BookStack\Entities\Tools\Markdown;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\Strikethrough\Strikethrough;
use League\CommonMark\Extension\Strikethrough\StrikethroughDelimiterProcessor;
class CustomStrikeThroughExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor());
$environment->addInlineRenderer(Strikethrough::class, new CustomStrikethroughRenderer());
}
}

View File

@ -0,0 +1,24 @@
<?php namespace BookStack\Entities\Tools\Markdown;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Strikethrough\Strikethrough;
use League\CommonMark\HtmlElement;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
/**
* This is a somewhat clone of the League\CommonMark\Extension\Strikethrough\StrikethroughRender
* class but modified slightly to use <s> HTML tags instead of <del> in order to
* match front-end markdown-it rendering.
*/
class CustomStrikethroughRenderer implements InlineRendererInterface
{
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
{
if (!($inline instanceof Strikethrough)) {
throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
}
return new HtmlElement('s', $inline->getData('attributes', []), $htmlRenderer->renderInlines($inline->children()));
}
}

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Entities\Tools; <?php namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
use DOMDocument; use DOMDocument;
use DOMNodeList; use DOMNodeList;
use DOMXPath; use DOMXPath;
@ -51,6 +52,7 @@ class PageContent
$environment = Environment::createCommonMarkEnvironment(); $environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension()); $environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension()); $environment->addExtension(new TaskListExtension());
$environment->addExtension(new CustomStrikeThroughExtension());
$converter = new CommonMarkConverter([], $environment); $converter = new CommonMarkConverter([], $environment);
return $converter->convertToHtml($markdown); return $converter->convertToHtml($markdown);
} }

View File

@ -41,6 +41,7 @@ class UserController extends Controller
'sort' => $request->get('sort', 'name'), 'sort' => $request->get('sort', 'name'),
]; ];
$users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails); $users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
$this->setPageTitle(trans('settings.users')); $this->setPageTitle(trans('settings.users'));
$users->appends($listDetails); $users->appends($listDetails);
return view('users.index', ['users' => $users, 'listDetails' => $listDetails]); return view('users.index', ['users' => $users, 'listDetails' => $listDetails]);

View File

@ -45,5 +45,5 @@ return [
// Other // Other
'commented_on' => 'yorum yaptı', 'commented_on' => 'yorum yaptı',
'permissions_update' => 'updated permissions', 'permissions_update' => 'güncellenmiş izinler',
]; ];

View File

@ -40,7 +40,7 @@ return [
'permissions_intro' => 'Etkinleştirildikten sonra bu izinler, diğer bütün izinlerden öncelikli olacaktır.', 'permissions_intro' => 'Etkinleştirildikten sonra bu izinler, diğer bütün izinlerden öncelikli olacaktır.',
'permissions_enable' => 'Özelleştirilmiş Yetkileri Etkinleştir', 'permissions_enable' => 'Özelleştirilmiş Yetkileri Etkinleştir',
'permissions_save' => 'İzinleri Kaydet', 'permissions_save' => 'İzinleri Kaydet',
'permissions_owner' => 'Owner', 'permissions_owner' => 'Sahip',
// Search // Search
'search_results' => 'Arama Sonuçları', 'search_results' => 'Arama Sonuçları',
@ -268,7 +268,7 @@ return [
'attachments_link_url' => 'Dosya bağlantısı', 'attachments_link_url' => 'Dosya bağlantısı',
'attachments_link_url_hint' => 'Dosyanın veya sitenin url adresi', 'attachments_link_url_hint' => 'Dosyanın veya sitenin url adresi',
'attach' => 'Ekle', 'attach' => 'Ekle',
'attachments_insert_link' => 'Add Attachment Link to Page', 'attachments_insert_link' => 'Sayfaya Bağlantı Ekle',
'attachments_edit_file' => 'Dosyayı Düzenle', 'attachments_edit_file' => 'Dosyayı Düzenle',
'attachments_edit_file_name' => 'Dosya Adı', 'attachments_edit_file_name' => 'Dosya Adı',
'attachments_edit_drop_upload' => 'Üzerine yazılacak dosyaları sürükleyin veya seçin', 'attachments_edit_drop_upload' => 'Üzerine yazılacak dosyaları sürükleyin veya seçin',

View File

@ -68,7 +68,7 @@ return [
'maint' => 'Bakım', 'maint' => 'Bakım',
'maint_image_cleanup' => 'Görselleri Temizle', 'maint_image_cleanup' => 'Görselleri Temizle',
'maint_image_cleanup_desc' => "Sayfaları ve revizyon içeriklerini tarayarak hangi görsellerin ve çizimlerin kullanımda olduğunu ve hangilerinin gereksiz olduğunu tespit eder. Bunu başlatmadan önce veritabanının ve görsellerin tam bir yedeğinin alındığından emin olun.", 'maint_image_cleanup_desc' => "Sayfaları ve revizyon içeriklerini tarayarak hangi görsellerin ve çizimlerin kullanımda olduğunu ve hangilerinin gereksiz olduğunu tespit eder. Bunu başlatmadan önce veritabanının ve görsellerin tam bir yedeğinin alındığından emin olun.",
'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions', 'maint_delete_images_only_in_revisions' => 'Eski sayfa revizyonlarındaki görselleri de sil',
'maint_image_cleanup_run' => 'Temizliği Başlat', 'maint_image_cleanup_run' => 'Temizliği Başlat',
'maint_image_cleanup_warning' => 'Muhtemelen kullanılmayan :count adet görsel bulundu. Bu görselleri silmek istediğinize emin misiniz?', 'maint_image_cleanup_warning' => 'Muhtemelen kullanılmayan :count adet görsel bulundu. Bu görselleri silmek istediğinize emin misiniz?',
'maint_image_cleanup_success' => 'Muhtemelen kullanılmayan :count adet görsel bulundu ve silindi!', 'maint_image_cleanup_success' => 'Muhtemelen kullanılmayan :count adet görsel bulundu ve silindi!',
@ -80,41 +80,41 @@ return [
'maint_send_test_email_mail_subject' => 'Deneme E-postası', 'maint_send_test_email_mail_subject' => 'Deneme E-postası',
'maint_send_test_email_mail_greeting' => 'E-posta iletimi çalışıyor gibi görünüyor!', 'maint_send_test_email_mail_greeting' => 'E-posta iletimi çalışıyor gibi görünüyor!',
'maint_send_test_email_mail_text' => 'Tebrikler! Eğer bu e-posta bildirimini alıyorsanız, e-posta ayarlarınız doğru bir şekilde ayarlanmış demektir.', 'maint_send_test_email_mail_text' => 'Tebrikler! Eğer bu e-posta bildirimini alıyorsanız, e-posta ayarlarınız doğru bir şekilde ayarlanmış demektir.',
'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.', 'maint_recycle_bin_desc' => 'Silinen raflar, kitaplar, bölümler ve sayfalar geri dönüşüm kutusuna gönderilir, böylece geri yüklenebilir veya kalıcı olarak silinebilir. Geri dönüşüm kutusundaki daha eski öğeler, sistem yapılandırmasına bağlı olarak bir süre sonra otomatik olarak kaldırılabilir.',
'maint_recycle_bin_open' => 'Open Recycle Bin', 'maint_recycle_bin_open' => 'Geri Dönüşüm Kutusunu Aç',
// Recycle Bin // Recycle Bin
'recycle_bin' => 'Recycle Bin', 'recycle_bin' => 'Geri Dönüşüm Kutusu',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', 'recycle_bin_desc' => 'Burada silinen öğeleri geri yükleyebilir veya bunları sistemden kalıcı olarak kaldırmayı seçebilirsiniz. Bu liste, izin filtrelerinin uygulandığı sistemdeki benzer etkinlik listelerinden farklı olarak filtrelenmez.',
'recycle_bin_deleted_item' => 'Deleted Item', 'recycle_bin_deleted_item' => 'Silinen öge',
'recycle_bin_deleted_by' => 'Deleted By', 'recycle_bin_deleted_by' => 'Tarafından silindi',
'recycle_bin_deleted_at' => 'Deletion Time', 'recycle_bin_deleted_at' => 'Silinme Zamanı',
'recycle_bin_permanently_delete' => 'Permanently Delete', 'recycle_bin_permanently_delete' => 'Kalıcı Olarak Sil',
'recycle_bin_restore' => 'Restore', 'recycle_bin_restore' => 'Geri Yükle',
'recycle_bin_contents_empty' => 'The recycle bin is currently empty', 'recycle_bin_contents_empty' => 'Geri dönüşüm kutusu boş',
'recycle_bin_empty' => 'Empty Recycle Bin', 'recycle_bin_empty' => 'Geri Dönüşüm Kutusunu Boşalt',
'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?', 'recycle_bin_empty_confirm' => 'Bu işlem, her bir öğenin içinde bulunan içerik de dahil olmak üzere geri dönüşüm kutusundaki tüm öğeleri kalıcı olarak imha edecektir. Geri dönüşüm kutusunu boşaltmak istediğinizden emin misiniz?',
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?', 'recycle_bin_destroy_confirm' => 'Bu işlem, bu öğeyi kalıcı olarak ve aşağıda listelenen alt öğelerle birlikte sistemden silecek ve bu içeriği geri yükleyemeyeceksiniz. Bu öğeyi kalıcı olarak silmek istediğinizden emin misiniz?',
'recycle_bin_destroy_list' => 'Items to be Destroyed', 'recycle_bin_destroy_list' => 'Kalıcı Olarak Silinecek Öğeler',
'recycle_bin_restore_list' => 'Items to be Restored', 'recycle_bin_restore_list' => 'Geri Yüklenecek Öğeler',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.', 'recycle_bin_restore_confirm' => 'Bu eylem, tüm alt öğeler dahil olmak üzere silinen öğeyi orijinal konumlarına geri yükleyecektir. Orijinal konum o zamandan beri silinmişse ve şimdi geri dönüşüm kutusunda bulunuyorsa, üst öğenin de geri yüklenmesi gerekecektir.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.', 'recycle_bin_restore_deleted_parent' => 'Bu öğenin üst öğesi de silindi. Bunlar, üst öğe de geri yüklenene kadar silinmiş olarak kalacaktır.',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.', 'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.', 'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
// Audit Log // Audit Log
'audit' => 'Audit Log', 'audit' => 'Denetim Kaydı',
'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', 'audit_desc' => 'Bu denetim günlüğü, sistemde izlenen etkinliklerin bir listesini görüntüler. Bu liste, izin filtrelerinin uygulandığı sistemdeki benzer etkinlik listelerinden farklı olarak filtrelenmez.',
'audit_event_filter' => 'Event Filter', 'audit_event_filter' => 'Etkinlik Filtresi',
'audit_event_filter_no_filter' => 'No Filter', 'audit_event_filter_no_filter' => 'Filtre Yok',
'audit_deleted_item' => 'Deleted Item', 'audit_deleted_item' => 'Silinen Öge',
'audit_deleted_item_name' => 'Name: :name', 'audit_deleted_item_name' => 'Isim: :name',
'audit_table_user' => 'User', 'audit_table_user' => 'Kullanıcı',
'audit_table_event' => 'Event', 'audit_table_event' => 'Etkinlik',
'audit_table_related' => 'Related Item or Detail', 'audit_table_related' => 'İlgili Öğe veya Detay',
'audit_table_date' => 'Activity Date', 'audit_table_date' => 'Aktivite Tarihi',
'audit_date_from' => 'Date Range From', 'audit_date_from' => 'Tarih Aralığından',
'audit_date_to' => 'Date Range To', 'audit_date_to' => 'Tarih Aralığına',
// Role Settings // Role Settings
'roles' => 'Roller', 'roles' => 'Roller',
@ -157,7 +157,7 @@ return [
'user_profile' => 'Kullanıcı Profili', 'user_profile' => 'Kullanıcı Profili',
'users_add_new' => 'Yeni Kullanıcı Ekle', 'users_add_new' => 'Yeni Kullanıcı Ekle',
'users_search' => 'Kullanıcı Ara', 'users_search' => 'Kullanıcı Ara',
'users_latest_activity' => 'Latest Activity', 'users_latest_activity' => 'Son Etkinlik',
'users_details' => 'Kullanıcı Detayları', 'users_details' => 'Kullanıcı Detayları',
'users_details_desc' => 'Bu kullanıcı için gösterilecek bir isim ve e-posta adresi belirleyin. Buraya yazacağınız e-posta adresi, uygulamaya giriş yaparken kullanılacaktır.', 'users_details_desc' => 'Bu kullanıcı için gösterilecek bir isim ve e-posta adresi belirleyin. Buraya yazacağınız e-posta adresi, uygulamaya giriş yaparken kullanılacaktır.',
'users_details_desc_no_email' => 'Diğer kullanıcılar tarafından tanınabilmesi için bir isim belirleyin.', 'users_details_desc_no_email' => 'Diğer kullanıcılar tarafından tanınabilmesi için bir isim belirleyin.',
@ -175,10 +175,10 @@ return [
'users_delete_named' => ':userName kullanıcısını sil ', 'users_delete_named' => ':userName kullanıcısını sil ',
'users_delete_warning' => 'Bu işlem \':userName\' kullanıcısını sistemden tamamen silecektir.', 'users_delete_warning' => 'Bu işlem \':userName\' kullanıcısını sistemden tamamen silecektir.',
'users_delete_confirm' => 'Bu kullanıcıyı tamamen silmek istediğinize emin misiniz?', 'users_delete_confirm' => 'Bu kullanıcıyı tamamen silmek istediğinize emin misiniz?',
'users_migrate_ownership' => 'Migrate Ownership', 'users_migrate_ownership' => 'Sahipliği Taşıyın',
'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.', 'users_migrate_ownership_desc' => 'Başka bir kullanıcının şu anda bu kullanıcıya ait olan tüm öğelerin sahibi olmasını istiyorsanız buradan bir kullanıcı seçin.',
'users_none_selected' => 'No user selected', 'users_none_selected' => 'Hiçbir kullanıcı seçilmedi',
'users_delete_success' => 'User successfully removed', 'users_delete_success' => 'Kullanıcı başarıyla kaldırıldı',
'users_edit' => 'Kullanıcıyı Düzenle', 'users_edit' => 'Kullanıcıyı Düzenle',
'users_edit_profile' => 'Profili Düzenle', 'users_edit_profile' => 'Profili Düzenle',
'users_edit_success' => 'Kullanıcı başarıyla güncellendi', 'users_edit_success' => 'Kullanıcı başarıyla güncellendi',

View File

@ -90,7 +90,7 @@ return [
'required_without' => ':values değerinin bulunmuyor olması, :attribute alanını zorunlu kılar.', 'required_without' => ':values değerinin bulunmuyor olması, :attribute alanını zorunlu kılar.',
'required_without_all' => ':values değerlerinden hiçbirinin bulunmuyor olması, :attribute alanını zorunlu kılar.', 'required_without_all' => ':values değerlerinden hiçbirinin bulunmuyor olması, :attribute alanını zorunlu kılar.',
'same' => ':attribute ve :other eşleşmelidir.', 'same' => ':attribute ve :other eşleşmelidir.',
'safe_url' => 'The provided link may not be safe.', 'safe_url' => 'Sağlanan bağlantı güvenli olmayabilir.',
'size' => [ 'size' => [
'numeric' => ':attribute, :size boyutunda olmalıdır.', 'numeric' => ':attribute, :size boyutunda olmalıdır.',
'file' => ':attribute, :size kilobayt olmalıdır.', 'file' => ':attribute, :size kilobayt olmalıdır.',

View File

@ -37,7 +37,7 @@
</th> </th>
<th>{{ trans('settings.role_user_roles') }}</th> <th>{{ trans('settings.role_user_roles') }}</th>
<th class="text-right"> <th class="text-right">
<a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'latest_activity']) }}">{{ trans('settings.users_latest_activity') }}</a> <a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'last_activity_at']) }}">{{ trans('settings.users_latest_activity') }}</a>
</th> </th>
</tr> </tr>
@foreach($users as $user) @foreach($users as $user)
@ -58,8 +58,8 @@
@endforeach @endforeach
</td> </td>
<td class="text-right text-muted"> <td class="text-right text-muted">
@if($user->latestActivity) @if($user->last_activity_at)
<small title="{{ $user->latestActivity->created_at->format('Y-m-d H:i:s') }}">{{ $user->latestActivity->created_at->diffForHumans() }}</small> <small title="{{ $user->last_activity_at->format('Y-m-d H:i:s') }}">{{ $user->last_activity_at->diffForHumans() }}</small>
@endif @endif
</td> </td>
</tr> </tr>

View File

@ -461,4 +461,22 @@ class PageContentTest extends TestCase
$pageView = $this->get($page->getUrl()); $pageView = $this->get($page->getUrl());
$pageView->assertElementExists('.page-content input[type=checkbox]'); $pageView->assertElementExists('.page-content input[type=checkbox]');
} }
public function test_page_markdown_strikethrough_rendering()
{
$this->asEditor();
$page = Page::query()->first();
$content = '~~some crossed out text~~';
$this->put($page->getUrl(), [
'name' => $page->name, 'markdown' => $content,
'html' => '', 'summary' => ''
]);
$page->refresh();
$this->assertStringMatchesFormat('%A<s%A>some crossed out text</s>%A', $page->html);
$pageView = $this->get($page->getUrl());
$pageView->assertElementExists('.page-content p > s');
}
} }