mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-02-22 20:10:56 +08:00
Added chapter move actions. Closes #86
This commit is contained in:
parent
e584b4926f
commit
9baa96d41c
@ -154,6 +154,55 @@ class ChapterController extends Controller
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the page for moving a chapter.
|
||||
* @param $bookSlug
|
||||
* @param $chapterSlug
|
||||
* @return mixed
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function showMove($bookSlug, $chapterSlug) {
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
return view('chapters/move', [
|
||||
'chapter' => $chapter,
|
||||
'book' => $book
|
||||
]);
|
||||
}
|
||||
|
||||
public function move($bookSlug, $chapterSlug, Request $request) {
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
|
||||
$entitySelection = $request->get('entity_selection', null);
|
||||
if ($entitySelection === null || $entitySelection === '') {
|
||||
return redirect($chapter->getUrl());
|
||||
}
|
||||
|
||||
$stringExploded = explode(':', $entitySelection);
|
||||
$entityType = $stringExploded[0];
|
||||
$entityId = intval($stringExploded[1]);
|
||||
|
||||
$parent = false;
|
||||
|
||||
if ($entityType == 'book') {
|
||||
$parent = $this->bookRepo->getById($entityId);
|
||||
}
|
||||
|
||||
if ($parent === false || $parent === null) {
|
||||
session()->flash('The selected Book was not found');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$this->chapterRepo->changeBook($parent->id, $chapter);
|
||||
Activity::add($chapter, 'chapter_move', $chapter->book->id);
|
||||
session()->flash('success', sprintf('Chapter moved to "%s"', $parent->name));
|
||||
|
||||
return redirect($chapter->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Restrictions view.
|
||||
* @param $bookSlug
|
||||
|
@ -468,6 +468,14 @@ class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the action of moving the location of a page
|
||||
* @param $bookSlug
|
||||
* @param $pageSlug
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function move($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
|
@ -55,6 +55,8 @@ Route::group(['middleware' => 'auth'], function () {
|
||||
Route::post('/{bookSlug}/chapter/create', 'ChapterController@store');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
|
||||
Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@showMove');
|
||||
Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
|
||||
Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
|
||||
|
@ -9,6 +9,18 @@ use BookStack\Chapter;
|
||||
|
||||
class ChapterRepo extends EntityRepo
|
||||
{
|
||||
protected $pageRepo;
|
||||
|
||||
/**
|
||||
* ChapterRepo constructor.
|
||||
* @param $pageRepo
|
||||
*/
|
||||
public function __construct(PageRepo $pageRepo)
|
||||
{
|
||||
$this->pageRepo = $pageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Base query for getting chapters, Takes permissions into account.
|
||||
* @return mixed
|
||||
@ -189,12 +201,21 @@ class ChapterRepo extends EntityRepo
|
||||
public function changeBook($bookId, Chapter $chapter)
|
||||
{
|
||||
$chapter->book_id = $bookId;
|
||||
// Update related activity
|
||||
foreach ($chapter->activity as $activity) {
|
||||
$activity->book_id = $bookId;
|
||||
$activity->save();
|
||||
}
|
||||
$chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
|
||||
$chapter->save();
|
||||
// Update all child pages
|
||||
foreach ($chapter->pages as $page) {
|
||||
$this->pageRepo->changeBook($bookId, $page);
|
||||
}
|
||||
// Update permissions
|
||||
$chapter->load('book');
|
||||
$this->permissionService->buildJointPermissionsForEntity($chapter->book);
|
||||
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ return [
|
||||
|
||||
/**
|
||||
* Activity text strings.
|
||||
* Is used for all the text within activity logs.
|
||||
* Is used for all the text within activity logs & notifications.
|
||||
*/
|
||||
|
||||
// Pages
|
||||
@ -25,6 +25,7 @@ return [
|
||||
'chapter_update_notification' => 'Chapter Successfully Updated',
|
||||
'chapter_delete' => 'deleted chapter',
|
||||
'chapter_delete_notification' => 'Chapter Successfully Deleted',
|
||||
'chapter_move' => 'moved chapter',
|
||||
|
||||
// Books
|
||||
'book_create' => 'created book',
|
||||
|
33
resources/views/chapters/move.blade.php
Normal file
33
resources/views/chapters/move.blade.php
Normal file
@ -0,0 +1,33 @@
|
||||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="faded-small toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 faded">
|
||||
<div class="breadcrumbs">
|
||||
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
|
||||
<span class="sep">»</span>
|
||||
<a href="{{$chapter->getUrl()}}" class="text-page text-button"><i class="zmdi zmdi-file-text"></i>{{ $chapter->getShortName() }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1>Move Chapter <small class="subheader">{{$chapter->name}}</small></h1>
|
||||
|
||||
<form action="{{ $chapter->getUrl() }}/move" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
@include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
|
||||
|
||||
<a href="{{ $chapter->getUrl() }}" class="button muted">Cancel</a>
|
||||
<button type="submit" class="button pos">Move Chapter</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@stop
|
@ -2,15 +2,15 @@
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="faded-small toolbar" ng-non-bindable>
|
||||
<div class="faded-small toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 faded">
|
||||
<div class="col-sm-8 faded" ng-non-bindable>
|
||||
<div class="breadcrumbs">
|
||||
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 faded">
|
||||
<div class="col-sm-4 faded">
|
||||
<div class="action-buttons">
|
||||
@if(userCan('page-create', $chapter))
|
||||
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
|
||||
@ -18,11 +18,21 @@
|
||||
@if(userCan('chapter-update', $chapter))
|
||||
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
|
||||
@endif
|
||||
@if(userCan('restrictions-manage', $chapter))
|
||||
<a href="{{$chapter->getUrl()}}/permissions" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Permissions</a>
|
||||
@endif
|
||||
@if(userCan('chapter-delete', $chapter))
|
||||
<a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
|
||||
@if(userCan('chapter-update', $chapter) || userCan('restrictions-manage', $chapter) || userCan('chapter-delete', $chapter))
|
||||
<div dropdown class="dropdown-container">
|
||||
<a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
|
||||
<ul>
|
||||
@if(userCan('chapter-update', $chapter))
|
||||
<li><a href="{{$chapter->getUrl() . '/move'}}" class="text-primary"><i class="zmdi zmdi-folder"></i>Move</a></li>
|
||||
@endif
|
||||
@if(userCan('restrictions-manage', $chapter))
|
||||
<li><a href="{{$chapter->getUrl()}}/permissions" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li>
|
||||
@endif
|
||||
@if(userCan('chapter-delete', $chapter))
|
||||
<li><a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,14 +30,7 @@
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
<div class="form-group">
|
||||
<div entity-selector class="entity-selector large" entity-types="book,chapter">
|
||||
<input type="hidden" entity-selector-input name="entity_selection" value="">
|
||||
<input type="text" placeholder="Search" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
|
||||
<div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div>
|
||||
<div ng-show="!loading" ng-bind-html="entityResults"></div>
|
||||
</div>
|
||||
</div>
|
||||
@include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
|
||||
|
||||
<a href="{{ $page->getUrl() }}" class="button muted">Cancel</a>
|
||||
<button type="submit" class="button pos">Move Page</button>
|
||||
|
8
resources/views/partials/entity-selector.blade.php
Normal file
8
resources/views/partials/entity-selector.blade.php
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="form-group">
|
||||
<div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
|
||||
<input type="hidden" entity-selector-input name="{{$name}}" value="">
|
||||
<input type="text" placeholder="Search" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
|
||||
<div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div>
|
||||
<div ng-show="!loading" ng-bind-html="entityResults"></div>
|
||||
</div>
|
||||
</div>
|
@ -40,4 +40,29 @@ class SortTest extends TestCase
|
||||
->seeInNthElement('.activity-list-item', 0, $page->name);
|
||||
}
|
||||
|
||||
public function test_chapter_move()
|
||||
{
|
||||
$chapter = \BookStack\Chapter::first();
|
||||
$currentBook = $chapter->book;
|
||||
$pageToCheck = $chapter->pages->first();
|
||||
$newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
|
||||
|
||||
$this->asAdmin()->visit($chapter->getUrl() . '/move')
|
||||
->see('Move Chapter')->see($chapter->name)
|
||||
->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter');
|
||||
|
||||
$chapter = \BookStack\Chapter::find($chapter->id);
|
||||
$this->seePageIs($chapter->getUrl());
|
||||
$this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
|
||||
|
||||
$this->visit($newBook->getUrl())
|
||||
->seeInNthElement('.activity-list-item', 0, 'moved chapter')
|
||||
->seeInNthElement('.activity-list-item', 0, $chapter->name);
|
||||
|
||||
$pageToCheck = \BookStack\Page::find($pageToCheck->id);
|
||||
$this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
|
||||
$this->visit($pageToCheck->getUrl())
|
||||
->see($newBook->name);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user