Merge branch 'master' into release
This commit is contained in:
commit
5a6d544db7
|
@ -324,9 +324,10 @@ class PageController extends Controller
|
||||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||||
$book = $page->book;
|
$book = $page->book;
|
||||||
$this->checkOwnablePermission('page-delete', $page);
|
$this->checkOwnablePermission('page-delete', $page);
|
||||||
|
$this->entityRepo->destroyPage($page);
|
||||||
|
|
||||||
Activity::addMessage('page_delete', $book->id, $page->name);
|
Activity::addMessage('page_delete', $book->id, $page->name);
|
||||||
session()->flash('success', trans('entities.pages_delete_success'));
|
session()->flash('success', trans('entities.pages_delete_success'));
|
||||||
$this->entityRepo->destroyPage($page);
|
|
||||||
return redirect($book->getUrl());
|
return redirect($book->getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use BookStack\Book;
|
||||||
use BookStack\Chapter;
|
use BookStack\Chapter;
|
||||||
use BookStack\Entity;
|
use BookStack\Entity;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
|
use BookStack\Exceptions\NotifyException;
|
||||||
use BookStack\Page;
|
use BookStack\Page;
|
||||||
use BookStack\PageRevision;
|
use BookStack\PageRevision;
|
||||||
use BookStack\Services\AttachmentService;
|
use BookStack\Services\AttachmentService;
|
||||||
|
@ -1073,6 +1074,7 @@ class EntityRepo
|
||||||
/**
|
/**
|
||||||
* Destroy a given page along with its dependencies.
|
* Destroy a given page along with its dependencies.
|
||||||
* @param Page $page
|
* @param Page $page
|
||||||
|
* @throws NotifyException
|
||||||
*/
|
*/
|
||||||
public function destroyPage(Page $page)
|
public function destroyPage(Page $page)
|
||||||
{
|
{
|
||||||
|
@ -1084,6 +1086,12 @@ class EntityRepo
|
||||||
$this->permissionService->deleteJointPermissionsForEntity($page);
|
$this->permissionService->deleteJointPermissionsForEntity($page);
|
||||||
$this->searchService->deleteEntityTerms($page);
|
$this->searchService->deleteEntityTerms($page);
|
||||||
|
|
||||||
|
// Check if set as custom homepage
|
||||||
|
$customHome = setting('app-homepage', '0:');
|
||||||
|
if (intval($page->id) === intval(explode(':', $customHome)[0])) {
|
||||||
|
throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
// Delete Attached Files
|
// Delete Attached Files
|
||||||
$attachmentService = app(AttachmentService::class);
|
$attachmentService = app(AttachmentService::class);
|
||||||
foreach ($page->attachments as $attachment) {
|
foreach ($page->attachments as $attachment) {
|
||||||
|
|
|
@ -382,7 +382,7 @@ class SearchService
|
||||||
protected function generateTermArrayFromText($text, $scoreAdjustment = 1)
|
protected function generateTermArrayFromText($text, $scoreAdjustment = 1)
|
||||||
{
|
{
|
||||||
$tokenMap = []; // {TextToken => OccurrenceCount}
|
$tokenMap = []; // {TextToken => OccurrenceCount}
|
||||||
$splitChars = " \n\t.,";
|
$splitChars = " \n\t.,!?:;()[]{}<>`'\"";
|
||||||
$token = strtok($text, $splitChars);
|
$token = strtok($text, $splitChars);
|
||||||
|
|
||||||
while ($token !== false) {
|
while ($token !== false) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ body.flexbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
max-width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
&.rows {
|
&.rows {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -41,6 +41,7 @@ return [
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
|
'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
|
||||||
|
'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
|
||||||
|
|
||||||
// Entities
|
// Entities
|
||||||
'entity_not_found' => 'Entity not found',
|
'entity_not_found' => 'Entity not found',
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
|
|
||||||
<div ng-non-bindable class="container small">
|
<div class="container small">
|
||||||
<h1>{{$book->name}}</h1>
|
<h1>{{$book->name}}</h1>
|
||||||
<div class="book-content" v-show="!searching">
|
<div class="book-content" v-show="!searching">
|
||||||
<p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
|
<p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
@extends('sidebar-layout')
|
@extends('sidebar-layout')
|
||||||
|
|
||||||
@section('toolbar')
|
@section('toolbar')
|
||||||
<div class="col-sm-6 col-xs-3 faded" ng-non-bindable>
|
<div class="col-sm-6 col-xs-3 faded" v-pre>
|
||||||
@include('chapters._breadcrumbs', ['chapter' => $chapter])
|
@include('chapters._breadcrumbs', ['chapter' => $chapter])
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-xs-9 faded">
|
<div class="col-sm-6 col-xs-9 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<span dropdown class="dropdown-container">
|
<span dropdown class="dropdown-container">
|
||||||
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
|
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
|
||||||
<ul class="wide">
|
<ul class="wide">
|
||||||
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
|
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
|
||||||
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
|
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
|
||||||
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
|
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
@if(userCan('page-create', $chapter))
|
@if(userCan('page-create', $chapter))
|
||||||
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
|
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
|
||||||
@endif
|
@endif
|
||||||
|
@ -96,13 +96,13 @@
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
|
|
||||||
<div class="container small" ng-non-bindable >
|
<div class="container small">
|
||||||
<h1>{{ $chapter->name }}</h1>
|
<h1 v-pre>{{ $chapter->name }}</h1>
|
||||||
<div class="chapter-content" v-show="!searching">
|
<div class="chapter-content" v-show="!searching">
|
||||||
<p class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
|
<p v-pre class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
|
||||||
|
|
||||||
@if(count($pages) > 0)
|
@if(count($pages) > 0)
|
||||||
<div class="page-list">
|
<div v-pre class="page-list">
|
||||||
<hr>
|
<hr>
|
||||||
@foreach($pages as $page)
|
@foreach($pages as $page)
|
||||||
@include('pages/list-item', ['page' => $page])
|
@include('pages/list-item', ['page' => $page])
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="well">
|
<div v-pre class="well">
|
||||||
<p class="text-muted italic">{{ trans('entities.chapters_empty') }}</p>
|
<p class="text-muted italic">{{ trans('entities.chapters_empty') }}</p>
|
||||||
<p>
|
<p>
|
||||||
@if(userCan('page-create', $chapter))
|
@if(userCan('page-create', $chapter))
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{--Title input--}}
|
{{--Title input--}}
|
||||||
<div class="title-input page-title clearfix" ng-non-bindable>
|
<div class="title-input page-title clearfix" v-pre>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
@include('form/text', ['name' => 'name', 'placeholder' => trans('entities.pages_title')])
|
@include('form/text', ['name' => 'name', 'placeholder' => trans('entities.pages_title')])
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
{{--WYSIWYG Editor--}}
|
{{--WYSIWYG Editor--}}
|
||||||
@if(setting('app-editor') === 'wysiwyg')
|
@if(setting('app-editor') === 'wysiwyg')
|
||||||
<div wysiwyg-editor class="flex-fill flex">
|
<div wysiwyg-editor class="flex-fill flex">
|
||||||
<textarea id="html-editor" name="html" rows="5" ng-non-bindable
|
<textarea id="html-editor" name="html" rows="5" v-pre
|
||||||
@if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
|
@if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
|
|
||||||
{{--Markdown Editor--}}
|
{{--Markdown Editor--}}
|
||||||
@if(setting('app-editor') === 'markdown')
|
@if(setting('app-editor') === 'markdown')
|
||||||
<div ng-non-bindable id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
|
<div v-pre id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
|
||||||
|
|
||||||
<div class="markdown-editor-wrap">
|
<div class="markdown-editor-wrap">
|
||||||
<div class="editor-toolbar">
|
<div class="editor-toolbar">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="right" ng-non-bindable>
|
<div class="right" v-pre>
|
||||||
@if($activity->user)
|
@if($activity->user)
|
||||||
<a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
|
<a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
|
||||||
@else
|
@else
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="card book-tree" ng-non-bindable>
|
<div class="card book-tree" v-pre>
|
||||||
<h3><i class="zmdi zmdi-book"></i> {{ trans('entities.books_navigation') }}</h3>
|
<h3><i class="zmdi zmdi-book"></i> {{ trans('entities.books_navigation') }}</h3>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<ul class="sidebar-page-list menu">
|
<ul class="sidebar-page-list menu">
|
||||||
|
|
|
@ -33,13 +33,13 @@ class EntitySearchTest extends TestCase
|
||||||
|
|
||||||
public function test_searching_accents_and_small_terms()
|
public function test_searching_accents_and_small_terms()
|
||||||
{
|
{
|
||||||
$page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content {a2 orange dog']);
|
$page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'some áéííúü¿¡ test content a2 orange dog']);
|
||||||
$this->asEditor();
|
$this->asEditor();
|
||||||
|
|
||||||
$accentSearch = $this->get('/search?term=' . urlencode('áéíí'));
|
$accentSearch = $this->get('/search?term=' . urlencode('áéíí'));
|
||||||
$accentSearch->assertStatus(200)->assertSee($page->name);
|
$accentSearch->assertStatus(200)->assertSee($page->name);
|
||||||
|
|
||||||
$smallSearch = $this->get('/search?term=' . urlencode('{a'));
|
$smallSearch = $this->get('/search?term=' . urlencode('a2'));
|
||||||
$smallSearch->assertStatus(200)->assertSee($page->name);
|
$smallSearch->assertStatus(200)->assertSee($page->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ class HomepageTest extends TestCase
|
||||||
$homeVisit->assertSee('Recent Activity');
|
$homeVisit->assertSee('Recent Activity');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_custom_homepage() {
|
public function test_custom_homepage()
|
||||||
|
{
|
||||||
$this->asEditor();
|
$this->asEditor();
|
||||||
$name = 'My custom homepage';
|
$name = 'My custom homepage';
|
||||||
$content = 'This is the body content of my custom homepage.';
|
$content = 'This is the body content of my custom homepage.';
|
||||||
|
@ -30,4 +31,26 @@ class HomepageTest extends TestCase
|
||||||
$homeVisit->assertSee('Recently Updated Pages');
|
$homeVisit->assertSee('Recently Updated Pages');
|
||||||
$homeVisit->assertSee('Recent Activity');
|
$homeVisit->assertSee('Recent Activity');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_delete_custom_homepage()
|
||||||
|
{
|
||||||
|
$this->asEditor();
|
||||||
|
$name = 'My custom homepage';
|
||||||
|
$content = 'This is the body content of my custom homepage.';
|
||||||
|
$customPage = $this->newPage(['name' => $name, 'html' => $content]);
|
||||||
|
$this->setSettings(['app-homepage' => $customPage->id]);
|
||||||
|
|
||||||
|
$homeVisit = $this->get('/');
|
||||||
|
$homeVisit->assertSee($name);
|
||||||
|
|
||||||
|
$pageDeleteReq = $this->delete($customPage->getUrl());
|
||||||
|
$pageDeleteReq->assertStatus(302);
|
||||||
|
$pageDeleteReq->assertRedirect($customPage->getUrl());
|
||||||
|
$pageDeleteReq->assertSessionHas('error');
|
||||||
|
$pageDeleteReq->assertSessionMissing('success');
|
||||||
|
|
||||||
|
$homeVisit = $this->get('/');
|
||||||
|
$homeVisit->assertSee($name);
|
||||||
|
$homeVisit->assertStatus(200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue