Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Зуев Егор
/
wiki.dev
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
Dan Brown
2016-06-12 12:14:14 +0100
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
7f99903fdbd3eb814f72319130bfc6ef130d0fe5
7f99903f
1 parent
97d011ac
Finished off page move functionality
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
244 additions
and
19 deletions
app/Http/Controllers/PageController.php
app/Http/Controllers/SearchController.php
app/Http/routes.php
app/Repos/PageRepo.php
app/Services/ViewService.php
resources/assets/js/directives.js
resources/assets/sass/_forms.scss
resources/assets/sass/styles.scss
resources/lang/en/activities.php
resources/views/books/list-item.blade.php
resources/views/chapters/list-item.blade.php
resources/views/pages/list-item.blade.php
resources/views/pages/move.blade.php
resources/views/pages/show.blade.php
resources/views/partials/custom-styles.blade.php
app/Http/Controllers/PageController.php
View file @
7f99903
...
...
@@ -468,6 +468,41 @@ class PageController extends Controller
]);
}
public
function
move
(
$bookSlug
,
$pageSlug
,
Request
$request
)
{
$book
=
$this
->
bookRepo
->
getBySlug
(
$bookSlug
);
$page
=
$this
->
pageRepo
->
getBySlug
(
$pageSlug
,
$book
->
id
);
$this
->
checkOwnablePermission
(
'page-update'
,
$page
);
$entitySelection
=
$request
->
get
(
'entity_selection'
,
null
);
if
(
$entitySelection
===
null
||
$entitySelection
===
''
)
{
return
redirect
(
$page
->
getUrl
());
}
$stringExploded
=
explode
(
':'
,
$entitySelection
);
$entityType
=
$stringExploded
[
0
];
$entityId
=
intval
(
$stringExploded
[
1
]);
$parent
=
false
;
if
(
$entityType
==
'chapter'
)
{
$parent
=
$this
->
chapterRepo
->
getById
(
$entityId
);
}
else
if
(
$entityType
==
'book'
)
{
$parent
=
$this
->
bookRepo
->
getById
(
$entityId
);
}
if
(
$parent
===
false
||
$parent
===
null
)
{
session
()
->
flash
(
'The selected Book or Chapter was not found'
);
return
redirect
()
->
back
();
}
$this
->
pageRepo
->
changePageParent
(
$page
,
$parent
);
Activity
::
add
(
$page
,
'page_move'
,
$page
->
book
->
id
);
session
()
->
flash
(
'success'
,
sprintf
(
'Page moved to "%s"'
,
$parent
->
name
));
return
redirect
(
$page
->
getUrl
());
}
/**
* Set the permissions for this page.
* @param $bookSlug
...
...
app/Http/Controllers/SearchController.php
View file @
7f99903
...
...
@@ -2,10 +2,10 @@
namespace
BookStack\Http\Controllers
;
use
BookStack\Services\ViewService
;
use
Illuminate\Http\Request
;
use
BookStack\Http\Requests
;
use
BookStack\Http\Controllers\Controller
;
use
BookStack\Repos\BookRepo
;
use
BookStack\Repos\ChapterRepo
;
use
BookStack\Repos\PageRepo
;
...
...
@@ -15,18 +15,21 @@ class SearchController extends Controller
protected
$pageRepo
;
protected
$bookRepo
;
protected
$chapterRepo
;
protected
$viewService
;
/**
* SearchController constructor.
* @param $pageRepo
* @param $bookRepo
* @param $chapterRepo
* @param PageRepo $pageRepo
* @param BookRepo $bookRepo
* @param ChapterRepo $chapterRepo
* @param ViewService $viewService
*/
public
function
__construct
(
PageRepo
$pageRepo
,
BookRepo
$bookRepo
,
ChapterRepo
$chapterRepo
)
public
function
__construct
(
PageRepo
$pageRepo
,
BookRepo
$bookRepo
,
ChapterRepo
$chapterRepo
,
ViewService
$viewService
)
{
$this
->
pageRepo
=
$pageRepo
;
$this
->
bookRepo
=
$bookRepo
;
$this
->
chapterRepo
=
$chapterRepo
;
$this
->
viewService
=
$viewService
;
parent
::
__construct
();
}
...
...
@@ -134,4 +137,35 @@ class SearchController extends Controller
return
view
(
'search/book'
,
[
'pages'
=>
$pages
,
'chapters'
=>
$chapters
,
'searchTerm'
=>
$searchTerm
]);
}
/**
* Search for a list of entities and return a partial HTML response of matching entities.
* Returns the most popular entities if no search is provided.
* @param Request $request
* @return mixed
*/
public
function
searchEntitiesAjax
(
Request
$request
)
{
$entities
=
collect
();
$entityTypes
=
$request
->
has
(
'types'
)
?
collect
(
explode
(
','
,
$request
->
get
(
'types'
)))
:
collect
([
'page'
,
'chapter'
,
'book'
]);
$searchTerm
=
(
$request
->
has
(
'term'
)
&&
trim
(
$request
->
get
(
'term'
))
!==
''
)
?
$request
->
get
(
'term'
)
:
false
;
// Search for entities otherwise show most popular
if
(
$searchTerm
!==
false
)
{
if
(
$entityTypes
->
contains
(
'page'
))
$entities
=
$entities
->
merge
(
$this
->
pageRepo
->
getBySearch
(
$searchTerm
)
->
items
());
if
(
$entityTypes
->
contains
(
'chapter'
))
$entities
=
$entities
->
merge
(
$this
->
chapterRepo
->
getBySearch
(
$searchTerm
)
->
items
());
if
(
$entityTypes
->
contains
(
'book'
))
$entities
=
$entities
->
merge
(
$this
->
bookRepo
->
getBySearch
(
$searchTerm
)
->
items
());
$entities
=
$entities
->
sortByDesc
(
'title_relevance'
);
}
else
{
$entityNames
=
$entityTypes
->
map
(
function
(
$type
)
{
return
'BookStack\\'
.
ucfirst
(
$type
);
})
->
toArray
();
$entities
=
$this
->
viewService
->
getPopular
(
20
,
0
,
$entityNames
);
}
return
view
(
'partials/entity-list'
,
[
'entities'
=>
$entities
]);
}
}
...
...
app/Http/routes.php
View file @
7f99903
...
...
@@ -35,6 +35,7 @@ Route::group(['middleware' => 'auth'], function () {
Route
::
get
(
'/{bookSlug}/page/{pageSlug}/export/plaintext'
,
'PageController@exportPlainText'
);
Route
::
get
(
'/{bookSlug}/page/{pageSlug}/edit'
,
'PageController@edit'
);
Route
::
get
(
'/{bookSlug}/page/{pageSlug}/move'
,
'PageController@showMove'
);
Route
::
put
(
'/{bookSlug}/page/{pageSlug}/move'
,
'PageController@move'
);
Route
::
get
(
'/{bookSlug}/page/{pageSlug}/delete'
,
'PageController@showDelete'
);
Route
::
get
(
'/{bookSlug}/draft/{pageId}/delete'
,
'PageController@showDeleteDraft'
);
Route
::
get
(
'/{bookSlug}/page/{pageSlug}/permissions'
,
'PageController@showRestrict'
);
...
...
@@ -94,6 +95,8 @@ Route::group(['middleware' => 'auth'], function () {
Route
::
post
(
'/update/{entityType}/{entityId}'
,
'TagController@updateForEntity'
);
});
Route
::
get
(
'/ajax/search/entities'
,
'SearchController@searchEntitiesAjax'
);
// Links
Route
::
get
(
'/link/{id}'
,
'PageController@redirectFromLink'
);
...
...
app/Repos/PageRepo.php
View file @
7f99903
...
...
@@ -3,6 +3,7 @@
use
Activity
;
use
BookStack\Book
;
use
BookStack\Chapter
;
use
BookStack\Entity
;
use
BookStack\Exceptions\NotFoundException
;
use
Carbon\Carbon
;
use
DOMDocument
;
...
...
@@ -572,6 +573,22 @@ class PageRepo extends EntityRepo
return
$page
;
}
/**
* Change the page's parent to the given entity.
* @param Page $page
* @param Entity $parent
*/
public
function
changePageParent
(
Page
$page
,
Entity
$parent
)
{
$book
=
$parent
->
isA
(
'book'
)
?
$parent
:
$parent
->
book
;
$page
->
chapter_id
=
$parent
->
isA
(
'chapter'
)
?
$parent
->
id
:
0
;
$page
->
save
();
$page
=
$this
->
changeBook
(
$book
->
id
,
$page
);
$page
->
load
(
'book'
);
$this
->
permissionService
->
buildJointPermissionsForEntity
(
$book
);
}
/**
* Gets a suitable slug for the resource
* @param $name
...
...
app/Services/ViewService.php
View file @
7f99903
...
...
@@ -50,7 +50,7 @@ class ViewService
* Get the entities with the most views.
* @param int $count
* @param int $page
* @param bool|false $filterModel
* @param bool|false
|array
$filterModel
*/
public
function
getPopular
(
$count
=
10
,
$page
=
0
,
$filterModel
=
false
)
{
...
...
@@ -60,7 +60,11 @@ class ViewService
->
groupBy
(
'viewable_id'
,
'viewable_type'
)
->
orderBy
(
'view_count'
,
'desc'
);
if
(
$filterModel
)
$query
->
where
(
'viewable_type'
,
'='
,
get_class
(
$filterModel
));
if
(
$filterModel
&&
is_array
(
$filterModel
))
{
$query
->
whereIn
(
'viewable_type'
,
$filterModel
);
}
else
if
(
$filterModel
)
{
$query
->
where
(
'viewable_type'
,
'='
,
get_class
(
$filterModel
));
};
return
$query
->
with
(
'viewable'
)
->
skip
(
$skipCount
)
->
take
(
$count
)
->
get
()
->
pluck
(
'viewable'
);
}
...
...
resources/assets/js/directives.js
View file @
7f99903
...
...
@@ -584,12 +584,62 @@ module.exports = function (ngApp, events) {
}]);
ngApp
.
directive
(
'entitySelector'
,
[
'$http'
,
function
(
$http
)
{
ngApp
.
directive
(
'entitySelector'
,
[
'$http'
,
'$sce'
,
function
(
$http
,
$sce
)
{
return
{
restrict
:
'A'
,
scope
:
true
,
link
:
function
(
scope
,
element
,
attrs
)
{
scope
.
loading
=
true
;
scope
.
entityResults
=
false
;
scope
.
search
=
''
;
// Add input for forms
const
input
=
element
.
find
(
'[entity-selector-input]'
).
first
();
// Listen to entity item clicks
element
.
on
(
'click'
,
'.entity-list a'
,
function
(
event
)
{
event
.
preventDefault
();
event
.
stopPropagation
();
let
item
=
$
(
this
).
closest
(
'[data-entity-type]'
);
itemSelect
(
item
);
});
element
.
on
(
'click'
,
'[data-entity-type]'
,
function
(
event
)
{
itemSelect
(
$
(
this
));
});
// Select entity action
function
itemSelect
(
item
)
{
let
entityType
=
item
.
attr
(
'data-entity-type'
);
let
entityId
=
item
.
attr
(
'data-entity-id'
);
let
isSelected
=
!
item
.
hasClass
(
'selected'
);
element
.
find
(
'.selected'
).
removeClass
(
'selected'
).
removeClass
(
'primary-background'
);
if
(
isSelected
)
item
.
addClass
(
'selected'
).
addClass
(
'primary-background'
);
let
newVal
=
isSelected
?
`
${
entityType
}
:
${
entityId
}
`
:
''
;
input
.
val
(
newVal
);
}
// Get search url with correct types
function
getSearchUrl
()
{
let
types
=
(
attrs
.
entityTypes
)
?
encodeURIComponent
(
attrs
.
entityTypes
)
:
encodeURIComponent
(
'page,book,chapter'
);
return
`/ajax/search/entities?types=
${
types
}
`
;
}
// Get initial contents
$http
.
get
(
getSearchUrl
()).
then
(
resp
=>
{
scope
.
entityResults
=
$sce
.
trustAsHtml
(
resp
.
data
);
scope
.
loading
=
false
;
});
// Search when typing
scope
.
searchEntities
=
function
()
{
scope
.
loading
=
true
;
input
.
val
(
''
);
let
url
=
getSearchUrl
()
+
'&term='
+
encodeURIComponent
(
scope
.
search
);
$http
.
get
(
url
).
then
(
resp
=>
{
scope
.
entityResults
=
$sce
.
trustAsHtml
(
resp
.
data
);
scope
.
loading
=
false
;
});
};
}
};
}]);
...
...
resources/assets/sass/_forms.scss
View file @
7f99903
...
...
@@ -20,6 +20,9 @@
&
.disabled
,
&
[
disabled
]
{
background
:
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==)
;
}
&
:focus
{
outline
:
0
;
}
}
#html-editor
{
...
...
resources/assets/sass/styles.scss
View file @
7f99903
...
...
@@ -207,3 +207,59 @@ $btt-size: 40px;
color
:
#EEE
;
}
}
.entity-selector
{
border
:
1px
solid
#DDD
;
border-radius
:
3px
;
overflow
:
hidden
;
font-size
:
0
.8em
;
input
[
type
=
"text"
]
{
width
:
100%
;
display
:
block
;
border-radius
:
0
;
border
:
0
;
border-bottom
:
1px
solid
#DDD
;
font-size
:
16px
;
padding
:
$-s
$-m
;
}
.entity-list
{
overflow-y
:
scroll
;
height
:
400px
;
background-color
:
#EEEEEE
;
}
.loading
{
height
:
400px
;
padding-top
:
$-l
;
}
.entity-list
>
p
{
text-align
:
center
;
padding-top
:
$-l
;
font-size
:
1
.333em
;
}
.entity-list
>
div
{
padding-left
:
$-m
;
padding-right
:
$-m
;
background-color
:
#FFF
;
transition
:
all
ease-in-out
120ms
;
cursor
:
pointer
;
}
}
.entity-list-item.selected
{
h3
,
i
,
p
,
a
{
color
:
#EEE
;
}
}
...
...
resources/lang/en/activities.php
View file @
7f99903
...
...
@@ -16,6 +16,7 @@ return [
'page_delete_notification'
=>
'Page Successfully Deleted'
,
'page_restore'
=>
'restored page'
,
'page_restore_notification'
=>
'Page Successfully Restored'
,
'page_move'
=>
'moved page'
,
// Chapters
'chapter_create'
=>
'created chapter'
,
...
...
resources/views/books/list-item.blade.php
View file @
7f99903
<div
class=
"book
"
data-entity-type=
"book"
data-entity-id=
"{{$book->id}}"
>
<div
class=
"book
entity-list-item"
data-entity-type=
"book"
data-entity-id=
"{{$book->id}}"
>
<h3
class=
"text-book"
><a
class=
"text-book"
href=
"{{$book->getUrl()}}"
><i
class=
"zmdi zmdi-book"
></i>
{{$book->name}}
</a></h3>
@if(isset($book->searchSnippet))
<p
class=
"text-muted"
>
{!! $book->searchSnippet !!}
</p>
...
...
resources/views/chapters/list-item.blade.php
View file @
7f99903
<div
class=
"chapter"
data-entity-type=
"chapter"
data-entity-id=
"{{$chapter->id}}"
>
<div
class=
"chapter
entity-list-item
"
data-entity-type=
"chapter"
data-entity-id=
"{{$chapter->id}}"
>
<h3>
<a
href=
"{{ $chapter->getUrl() }}"
class=
"text-chapter"
>
<i
class=
"zmdi zmdi-collection-bookmark"
></i>
{{ $chapter->name }}
...
...
resources/views/pages/list-item.blade.php
View file @
7f99903
<div
class=
"page {{$page->draft ? 'draft' : ''}}"
data-entity-type=
"page"
data-entity-id=
"{{$page->id}}"
>
<div
class=
"page {{$page->draft ? 'draft' : ''}}
entity-list-item
"
data-entity-type=
"page"
data-entity-id=
"{{$page->id}}"
>
<h3>
<a
href=
"{{ $page->getUrl() }}"
class=
"text-page"
><i
class=
"zmdi zmdi-file-text"
></i>
{{ $page->name }}
</a>
</h3>
...
...
resources/views/pages/move.blade.php
View file @
7f99903
...
...
@@ -26,10 +26,22 @@
<div
class=
"container"
>
<h1>
Move Page
<small
class=
"subheader"
>
{{$page->name}}
</small></h1>
<div
class=
"bordered"
ng-cloak
entity-selector
>
<input
type=
"text"
placeholder=
"Search"
>
<div
class=
"text-center"
ng-if=
"loading"
>
@include('partials/loading-icon')
</div>
<form
action=
"{{ $page->getUrl() }}/move"
method=
"POST"
>
{!! 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"
>
<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>
<a
href=
"{{ $page->getUrl() }}"
class=
"button muted"
>
Cancel
</a>
<button
type=
"submit"
class=
"button pos"
>
Move Page
</button>
</form>
</div>
@stop
...
...
resources/views/pages/show.blade.php
View file @
7f99903
...
...
@@ -28,16 +28,26 @@
</ul>
</span>
@if(userCan('page-update', $page))
<a
href=
"{{$page->getUrl()}}/revisions"
class=
"text-primary text-button"
><i
class=
"zmdi zmdi-replay"
></i>
Revisions
</a>
<a
href=
"{{$page->getUrl()}}/edit"
class=
"text-primary text-button"
><i
class=
"zmdi zmdi-edit"
></i>
Edit
</a>
<a
href=
"{{$page->getUrl()}}/move"
class=
"text-primary text-button"
><i
class=
"zmdi zmdi-folder"
></i>
Move
</a>
@endif
@if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
<div
dropdown
class=
"dropdown-container"
>
<a
dropdown-toggle
class=
"text-primary text-button"
><i
class=
"zmdi zmdi-more-vert"
></i></a>
<ul>
@if(userCan('page-update', $page))
<li><a
href=
"{{$page->getUrl()}}/move"
class=
"text-primary"
><i
class=
"zmdi zmdi-folder"
></i>
Move
</a></li>
<li><a
href=
"{{$page->getUrl()}}/revisions"
class=
"text-primary"
><i
class=
"zmdi zmdi-replay"
></i>
Revisions
</a></li>
@endif
@if(userCan('restrictions-manage', $page))
<a
href=
"{{$page->getUrl()}}/permissions"
class=
"text-primary text-button"
><i
class=
"zmdi zmdi-lock-outline"
></i>
Permissions
</a
>
<li><a
href=
"{{$page->getUrl()}}/permissions"
class=
"text-primary"
><i
class=
"zmdi zmdi-lock-outline"
></i>
Permissions
</a></li
>
@endif
@if(userCan('page-delete', $page))
<a
href=
"{{$page->getUrl()}}/delete"
class=
"text-neg text-button"
><i
class=
"zmdi zmdi-delete"
></i>
Delete
</a>
<li><a
href=
"{{$page->getUrl()}}/delete"
class=
"text-neg"
><i
class=
"zmdi zmdi-delete"
></i>
Delete
</a></li>
@endif
</ul>
</div>
@endif
</div>
</div>
</div>
...
...
resources/views/partials/custom-styles.blade.php
View file @
7f99903
@if(Setting::get('app-color'))
<style>
header
,
#back-to-top
,
.primary-background
{
background-color
:
{{
Setting
::
get
(
'app-color'
)
}
}
;
background-color
:
{{
Setting
::
get
(
'app-color'
)
}
}
!
important
;
}
.faded-small
,
.primary-background-light
{
background-color
:
{{
Setting
::
get
(
'app-color-light'
)
}
}
;
...
...
Please
register
or
sign in
to post a comment