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-03-12 16:31:37 +0000
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Commit
ced8c8e4978a7458f1e3d2b75af64917cd50ec7d
ced8c8e4
2 parents
e9c213f8
bf7852ce
Merged branch autosaving_drafts into master
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
473 additions
and
38 deletions
app/Http/Controllers/PageController.php
app/Http/routes.php
app/Page.php
app/PageRevision.php
app/Repos/PageRepo.php
database/migrations/2016_03_09_203143_add_page_revision_types.php
public/uploads/.gitignore
resources/assets/js/controllers.js
resources/assets/js/directives.js
resources/assets/js/global.js
resources/assets/js/pages/page-form.js
resources/assets/sass/_header.scss
resources/assets/sass/_variables.scss
resources/assets/sass/styles.scss
resources/views/pages/create.blade.php
resources/views/pages/edit.blade.php
resources/views/pages/form.blade.php
resources/views/pages/revisions.blade.php
resources/views/partials/notifications.blade.php
tests/EntitySearchTest.php → tests/Entity/EntitySearchTest.php
tests/EntityTest.php → tests/Entity/EntityTest.php
tests/Entity/PageUpdateDraftTest.php
tests/RestrictionsTest.php → tests/Permissions/RestrictionsTest.php
tests/RolesTest.php → tests/Permissions/RolesTest.php
app/Http/Controllers/PageController.php
View file @
ced8c8e
...
...
@@ -108,6 +108,17 @@ class PageController extends Controller
}
/**
* Get page from an ajax request.
* @param $pageId
* @return \Illuminate\Http\JsonResponse
*/
public
function
getPageAjax
(
$pageId
)
{
$page
=
$this
->
pageRepo
->
getById
(
$pageId
);
return
response
()
->
json
(
$page
);
}
/**
* Show the form for editing the specified page.
* @param $bookSlug
* @param $pageSlug
...
...
@@ -119,6 +130,24 @@ class PageController extends Controller
$page
=
$this
->
pageRepo
->
getBySlug
(
$pageSlug
,
$book
->
id
);
$this
->
checkOwnablePermission
(
'page-update'
,
$page
);
$this
->
setPageTitle
(
'Editing Page '
.
$page
->
getShortName
());
$page
->
isDraft
=
false
;
// Check for active editing and drafts
$warnings
=
[];
if
(
$this
->
pageRepo
->
isPageEditingActive
(
$page
,
60
))
{
$warnings
[]
=
$this
->
pageRepo
->
getPageEditingActiveMessage
(
$page
,
60
);
}
if
(
$this
->
pageRepo
->
hasUserGotPageDraft
(
$page
,
$this
->
currentUser
->
id
))
{
$draft
=
$this
->
pageRepo
->
getUserPageDraft
(
$page
,
$this
->
currentUser
->
id
);
$page
->
name
=
$draft
->
name
;
$page
->
html
=
$draft
->
html
;
$page
->
isDraft
=
true
;
$warnings
[]
=
$this
->
pageRepo
->
getUserPageDraftMessage
(
$draft
);
}
if
(
count
(
$warnings
)
>
0
)
session
()
->
flash
(
'warning'
,
implode
(
"
\n
"
,
$warnings
));
return
view
(
'pages/edit'
,
[
'page'
=>
$page
,
'book'
=>
$book
,
'current'
=>
$page
]);
}
...
...
@@ -143,6 +172,24 @@ class PageController extends Controller
}
/**
* Save a draft update as a revision.
* @param Request $request
* @param $pageId
* @return \Illuminate\Http\JsonResponse
*/
public
function
saveUpdateDraft
(
Request
$request
,
$pageId
)
{
$this
->
validate
(
$request
,
[
'name'
=>
'required|string|max:255'
]);
$page
=
$this
->
pageRepo
->
getById
(
$pageId
);
$this
->
checkOwnablePermission
(
'page-update'
,
$page
);
$draft
=
$this
->
pageRepo
->
saveUpdateDraft
(
$page
,
$request
->
only
([
'name'
,
'html'
]));
$updateTime
=
$draft
->
updated_at
->
format
(
'H:i'
);
return
response
()
->
json
([
'status'
=>
'success'
,
'message'
=>
'Draft saved at '
.
$updateTime
]);
}
/**
* Redirect from a special link url which
* uses the page id rather than the name.
* @param $pageId
...
...
app/Http/routes.php
View file @
ced8c8e
...
...
@@ -75,6 +75,10 @@ Route::group(['middleware' => 'auth'], function () {
Route
::
delete
(
'/{imageId}'
,
'ImageController@destroy'
);
});
// Ajax routes
Route
::
put
(
'/ajax/page/{id}/save-draft'
,
'PageController@saveUpdateDraft'
);
Route
::
get
(
'/ajax/page/{id}'
,
'PageController@getPageAjax'
);
// Links
Route
::
get
(
'/link/{id}'
,
'PageController@redirectFromLink'
);
...
...
app/Page.php
View file @
ced8c8e
...
...
@@ -34,7 +34,7 @@ class Page extends Entity
public
function
revisions
()
{
return
$this
->
hasMany
(
'BookStack\PageRevision'
)
->
orderBy
(
'created_at'
,
'desc'
);
return
$this
->
hasMany
(
'BookStack\PageRevision'
)
->
where
(
'type'
,
'='
,
'version'
)
->
orderBy
(
'created_at'
,
'desc'
);
}
public
function
getUrl
()
...
...
app/PageRevision.php
View file @
ced8c8e
<?php
namespace
BookStack
;
<?php
namespace
BookStack
;
use
Illuminate\Database\Eloquent\Model
;
...
...
@@ -8,16 +6,28 @@ class PageRevision extends Model
{
protected
$fillable
=
[
'name'
,
'html'
,
'text'
];
/**
* Get the user that created the page revision
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public
function
createdBy
()
{
return
$this
->
belongsTo
(
'BookStack\User'
,
'created_by'
);
}
/**
* Get the page this revision originates from.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public
function
page
()
{
return
$this
->
belongsTo
(
'BookStack\Page'
);
}
/**
* Get the url for this revision.
* @return string
*/
public
function
getUrl
()
{
return
$this
->
page
->
getUrl
()
.
'/revisions/'
.
$this
->
id
;
...
...
app/Repos/PageRepo.php
View file @
ced8c8e
...
...
@@ -4,6 +4,8 @@
use
Activity
;
use
BookStack\Book
;
use
BookStack\Exceptions\NotFoundException
;
use
Carbon\Carbon
;
use
DOMDocument
;
use
Illuminate\Support\Str
;
use
BookStack\Page
;
use
BookStack\PageRevision
;
...
...
@@ -66,9 +68,10 @@ class PageRepo extends EntityRepo
public
function
findPageUsingOldSlug
(
$pageSlug
,
$bookSlug
)
{
$revision
=
$this
->
pageRevision
->
where
(
'slug'
,
'='
,
$pageSlug
)
->
whereHas
(
'page'
,
function
(
$query
)
{
->
whereHas
(
'page'
,
function
(
$query
)
{
$this
->
restrictionService
->
enforcePageRestrictions
(
$query
);
})
->
where
(
'type'
,
'='
,
'version'
)
->
where
(
'book_slug'
,
'='
,
$bookSlug
)
->
orderBy
(
'created_at'
,
'desc'
)
->
with
(
'page'
)
->
first
();
return
$revision
!==
null
?
$revision
->
page
:
null
;
...
...
@@ -100,8 +103,8 @@ class PageRepo extends EntityRepo
* Save a new page into the system.
* Input validation must be done beforehand.
* @param array $input
* @param Book
$book
* @param int
$chapterId
* @param Book $book
* @param int $chapterId
* @return Page
*/
public
function
saveNew
(
array
$input
,
Book
$book
,
$chapterId
=
null
)
...
...
@@ -128,9 +131,9 @@ class PageRepo extends EntityRepo
*/
protected
function
formatHtml
(
$htmlText
)
{
if
(
$htmlText
==
''
)
return
$htmlText
;
if
(
$htmlText
==
''
)
return
$htmlText
;
libxml_use_internal_errors
(
true
);
$doc
=
new
\
DOMDocument
();
$doc
=
new
DOMDocument
();
$doc
->
loadHTML
(
mb_convert_encoding
(
$htmlText
,
'HTML-ENTITIES'
,
'UTF-8'
));
$container
=
$doc
->
documentElement
;
...
...
@@ -239,8 +242,8 @@ class PageRepo extends EntityRepo
/**
* Updates a page with any fillable data and saves it into the database.
* @param Page
$page
* @param int
$book_id
* @param Page $page
* @param int $book_id
* @param string $input
* @return Page
*/
...
...
@@ -257,11 +260,16 @@ class PageRepo extends EntityRepo
}
// Update with new details
$userId
=
auth
()
->
user
()
->
id
;
$page
->
fill
(
$input
);
$page
->
html
=
$this
->
formatHtml
(
$input
[
'html'
]);
$page
->
text
=
strip_tags
(
$page
->
html
);
$page
->
updated_by
=
auth
()
->
user
()
->
i
d
;
$page
->
updated_by
=
$userI
d
;
$page
->
save
();
// Remove all update drafts for this user & page.
$this
->
userUpdateDraftsQuery
(
$page
,
$userId
)
->
delete
();
return
$page
;
}
...
...
@@ -297,6 +305,7 @@ class PageRepo extends EntityRepo
$revision
->
book_slug
=
$page
->
book
->
slug
;
$revision
->
created_by
=
auth
()
->
user
()
->
id
;
$revision
->
created_at
=
$page
->
updated_at
;
$revision
->
type
=
'version'
;
$revision
->
save
();
// Clear old revisions
if
(
$this
->
pageRevision
->
where
(
'page_id'
,
'='
,
$page
->
id
)
->
count
()
>
50
)
{
...
...
@@ -307,6 +316,134 @@ class PageRepo extends EntityRepo
}
/**
* Save a page update draft.
* @param Page $page
* @param array $data
* @return PageRevision
*/
public
function
saveUpdateDraft
(
Page
$page
,
$data
=
[])
{
$userId
=
auth
()
->
user
()
->
id
;
$drafts
=
$this
->
userUpdateDraftsQuery
(
$page
,
$userId
)
->
get
();
if
(
$drafts
->
count
()
>
0
)
{
$draft
=
$drafts
->
first
();
}
else
{
$draft
=
$this
->
pageRevision
->
newInstance
();
$draft
->
page_id
=
$page
->
id
;
$draft
->
slug
=
$page
->
slug
;
$draft
->
book_slug
=
$page
->
book
->
slug
;
$draft
->
created_by
=
$userId
;
$draft
->
type
=
'update_draft'
;
}
$draft
->
fill
(
$data
);
$draft
->
save
();
return
$draft
;
}
/**
* The base query for getting user update drafts.
* @param Page $page
* @param $userId
* @return mixed
*/
private
function
userUpdateDraftsQuery
(
Page
$page
,
$userId
)
{
return
$this
->
pageRevision
->
where
(
'created_by'
,
'='
,
$userId
)
->
where
(
'type'
,
'update_draft'
)
->
where
(
'page_id'
,
'='
,
$page
->
id
)
->
orderBy
(
'created_at'
,
'desc'
);
}
/**
* Checks whether a user has a draft version of a particular page or not.
* @param Page $page
* @param $userId
* @return bool
*/
public
function
hasUserGotPageDraft
(
Page
$page
,
$userId
)
{
return
$this
->
userUpdateDraftsQuery
(
$page
,
$userId
)
->
count
()
>
0
;
}
/**
* Get the latest updated draft revision for a particular page and user.
* @param Page $page
* @param $userId
* @return mixed
*/
public
function
getUserPageDraft
(
Page
$page
,
$userId
)
{
return
$this
->
userUpdateDraftsQuery
(
$page
,
$userId
)
->
first
();
}
/**
* Get the notification message that informs the user that they are editing a draft page.
* @param PageRevision $draft
* @return string
*/
public
function
getUserPageDraftMessage
(
PageRevision
$draft
)
{
$message
=
'You are currently editing a draft that was last saved '
.
$draft
->
updated_at
->
diffForHumans
()
.
'.'
;
if
(
$draft
->
page
->
updated_at
->
timestamp
>
$draft
->
updated_at
->
timestamp
)
{
$message
.=
"
\n
This page has been updated by since that time. It is recommended that you discard this draft."
;
}
return
$message
;
}
/**
* Check if a page is being actively editing.
* Checks for edits since last page updated.
* Passing in a minuted range will check for edits
* within the last x minutes.
* @param Page $page
* @param null $minRange
* @return bool
*/
public
function
isPageEditingActive
(
Page
$page
,
$minRange
=
null
)
{
$draftSearch
=
$this
->
activePageEditingQuery
(
$page
,
$minRange
);
return
$draftSearch
->
count
()
>
0
;
}
/**
* Get a notification message concerning the editing activity on
* a particular page.
* @param Page $page
* @param null $minRange
* @return string
*/
public
function
getPageEditingActiveMessage
(
Page
$page
,
$minRange
=
null
)
{
$pageDraftEdits
=
$this
->
activePageEditingQuery
(
$page
,
$minRange
)
->
get
();
$userMessage
=
$pageDraftEdits
->
count
()
>
1
?
$pageDraftEdits
->
count
()
.
' users have'
:
$pageDraftEdits
->
first
()
->
createdBy
->
name
.
' has'
;
$timeMessage
=
$minRange
===
null
?
'since the page was last updated'
:
'in the last '
.
$minRange
.
' minutes'
;
$message
=
'%s started editing this page %s. Take care not to overwrite each other\'s updates!'
;
return
sprintf
(
$message
,
$userMessage
,
$timeMessage
);
}
/**
* A query to check for active update drafts on a particular page.
* @param Page $page
* @param null $minRange
* @return mixed
*/
private
function
activePageEditingQuery
(
Page
$page
,
$minRange
=
null
)
{
$query
=
$this
->
pageRevision
->
where
(
'type'
,
'='
,
'update_draft'
)
->
where
(
'updated_at'
,
'>'
,
$page
->
updated_at
)
->
where
(
'created_by'
,
'!='
,
auth
()
->
user
()
->
id
)
->
with
(
'createdBy'
);
if
(
$minRange
!==
null
)
{
$query
=
$query
->
where
(
'updated_at'
,
'>='
,
Carbon
::
now
()
->
subMinutes
(
$minRange
));
}
return
$query
;
}
/**
* Gets a single revision via it's id.
* @param $id
* @return mixed
...
...
@@ -333,7 +470,7 @@ class PageRepo extends EntityRepo
/**
* Changes the related book for the specified page.
* Changes the book id of any relations to the page that store the book id.
* @param int
$bookId
* @param int $bookId
* @param Page $page
* @return Page
*/
...
...
database/migrations/2016_03_09_203143_add_page_revision_types.php
0 → 100644
View file @
ced8c8e
<?php
use
Illuminate\Database\Schema\Blueprint
;
use
Illuminate\Database\Migrations\Migration
;
class
AddPageRevisionTypes
extends
Migration
{
/**
* Run the migrations.
*
* @return void
*/
public
function
up
()
{
Schema
::
table
(
'page_revisions'
,
function
(
Blueprint
$table
)
{
$table
->
string
(
'type'
)
->
default
(
'version'
);
$table
->
index
(
'type'
);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public
function
down
()
{
Schema
::
table
(
'page_revisions'
,
function
(
Blueprint
$table
)
{
$table
->
dropColumn
(
'type'
);
});
}
}
public/uploads/.gitignore
100644 → 100755
View file @
ced8c8e
File mode changed
resources/assets/js/controllers.js
View file @
ced8c8e
...
...
@@ -213,4 +213,85 @@ module.exports = function (ngApp, events) {
}]);
ngApp
.
controller
(
'PageEditController'
,
[
'$scope'
,
'$http'
,
'$attrs'
,
'$interval'
,
'$timeout'
,
function
(
$scope
,
$http
,
$attrs
,
$interval
,
$timeout
)
{
$scope
.
editorOptions
=
require
(
'./pages/page-form'
);
$scope
.
editorHtml
=
''
;
$scope
.
draftText
=
''
;
var
pageId
=
Number
(
$attrs
.
pageId
);
var
isEdit
=
pageId
!==
0
;
var
autosaveFrequency
=
30
;
// AutoSave interval in seconds.
$scope
.
isDraft
=
Number
(
$attrs
.
pageDraft
)
===
1
;
if
(
$scope
.
isDraft
)
$scope
.
draftText
=
'Editing Draft'
;
var
autoSave
=
false
;
var
currentContent
=
{
title
:
false
,
html
:
false
};
if
(
isEdit
)
{
setTimeout
(()
=>
{
startAutoSave
();
},
1000
);
}
$scope
.
editorChange
=
function
()
{}
/**
* Start the AutoSave loop, Checks for content change
* before performing the costly AJAX request.
*/
function
startAutoSave
()
{
currentContent
.
title
=
$
(
'#name'
).
val
();
currentContent
.
html
=
$scope
.
editorHtml
;
autoSave
=
$interval
(()
=>
{
var
newTitle
=
$
(
'#name'
).
val
();
var
newHtml
=
$scope
.
editorHtml
;
if
(
newTitle
!==
currentContent
.
title
||
newHtml
!==
currentContent
.
html
)
{
currentContent
.
html
=
newHtml
;
currentContent
.
title
=
newTitle
;
saveDraftUpdate
(
newTitle
,
newHtml
);
}
},
1000
*
autosaveFrequency
);
}
/**
* Save a draft update into the system via an AJAX request.
* @param title
* @param html
*/
function
saveDraftUpdate
(
title
,
html
)
{
$http
.
put
(
'/ajax/page/'
+
pageId
+
'/save-draft'
,
{
name
:
title
,
html
:
html
}).
then
((
responseData
)
=>
{
$scope
.
draftText
=
responseData
.
data
.
message
;
$scope
.
isDraft
=
true
;
});
}
/**
* Discard the current draft and grab the current page
* content from the system via an AJAX request.
*/
$scope
.
discardDraft
=
function
()
{
$http
.
get
(
'/ajax/page/'
+
pageId
).
then
((
responseData
)
=>
{
if
(
autoSave
)
$interval
.
cancel
(
autoSave
);
$scope
.
draftText
=
''
;
$scope
.
isDraft
=
false
;
$scope
.
$broadcast
(
'html-update'
,
responseData
.
data
.
html
);
$
(
'#name'
).
val
(
currentContent
.
title
);
$timeout
(()
=>
{
startAutoSave
();
},
1000
);
events
.
emit
(
'success'
,
'Draft discarded, The editor has been updated with the current page content'
);
});
};
}]);
};
\ No newline at end of file
...
...
resources/assets/js/directives.js
View file @
ced8c8e
...
...
@@ -162,5 +162,42 @@ module.exports = function (ngApp, events) {
};
}]);
ngApp
.
directive
(
'tinymce'
,
[
'$timeout'
,
function
(
$timeout
)
{
return
{
restrict
:
'A'
,
scope
:
{
tinymce
:
'='
,
mceModel
:
'='
,
mceChange
:
'='
},
link
:
function
(
scope
,
element
,
attrs
)
{
function
tinyMceSetup
(
editor
)
{
editor
.
on
(
'ExecCommand change NodeChange ObjectResized'
,
(
e
)
=>
{
var
content
=
editor
.
getContent
();
$timeout
(()
=>
{
scope
.
mceModel
=
content
;
});
scope
.
mceChange
(
content
);
});
editor
.
on
(
'init'
,
(
e
)
=>
{
scope
.
mceModel
=
editor
.
getContent
();
});
scope
.
$on
(
'html-update'
,
(
event
,
value
)
=>
{
editor
.
setContent
(
value
);
editor
.
selection
.
select
(
editor
.
getBody
(),
true
);
editor
.
selection
.
collapse
(
false
);
scope
.
mceModel
=
editor
.
getContent
();
});
}
scope
.
tinymce
.
extraSetups
.
push
(
tinyMceSetup
);
tinymce
.
init
(
scope
.
tinymce
);
}
}
}])
};
\ No newline at end of file
...
...
resources/assets/js/global.js
View file @
ced8c8e
...
...
@@ -54,10 +54,10 @@ $.expr[":"].contains = $.expr.createPseudo(function (arg) {
// Global jQuery Elements
$
(
function
()
{
var
notifications
=
$
(
'.notification'
);
var
successNotification
=
notifications
.
filter
(
'.pos'
);
var
errorNotification
=
notifications
.
filter
(
'.neg'
);
var
warningNotification
=
notifications
.
filter
(
'.warning'
);
// Notification Events
window
.
Events
.
listen
(
'success'
,
function
(
text
)
{
successNotification
.
hide
();
...
...
@@ -66,6 +66,10 @@ $(function () {
successNotification
.
show
();
},
1
);
});
window
.
Events
.
listen
(
'warning'
,
function
(
text
)
{
warningNotification
.
find
(
'span'
).
text
(
text
);
warningNotification
.
show
();
});
window
.
Events
.
listen
(
'error'
,
function
(
text
)
{
errorNotification
.
find
(
'span'
).
text
(
text
);
errorNotification
.
show
();
...
...
@@ -119,11 +123,5 @@ function elemExists(selector) {
return
document
.
querySelector
(
selector
)
!==
null
;
}
// TinyMCE editor
if
(
elemExists
(
'#html-editor'
))
{
var
tinyMceOptions
=
require
(
'./pages/page-form'
);
tinymce
.
init
(
tinyMceOptions
);
}
// Page specific items
require
(
'./pages/page-show'
);
\ No newline at end of file
require
(
'./pages/page-show'
);
...
...
resources/assets/js/pages/page-form.js
View file @
ced8c8e
module
.
exports
=
{
var
mceOptions
=
module
.
exports
=
{
selector
:
'#html-editor'
,
content_css
:
[
'/css/styles.css'
...
...
@@ -51,8 +51,13 @@ module.exports = {
args
.
content
=
''
;
}
},
extraSetups
:
[],
setup
:
function
(
editor
)
{
for
(
var
i
=
0
;
i
<
mceOptions
.
extraSetups
.
length
;
i
++
)
{
mceOptions
.
extraSetups
[
i
](
editor
);
}
(
function
()
{
var
wrap
;
...
...
resources/assets/sass/_header.scss
View file @
ced8c8e
...
...
@@ -161,6 +161,12 @@ form.search-box {
}
}
.faded
span
.faded-text
{
display
:
inline-block
;
padding
:
$-s
;
opacity
:
0
.5
;
}
.faded-small
{
color
:
#000
;
font-size
:
0
.9em
;
...
...
@@ -183,6 +189,9 @@ form.search-box {
padding-left
:
0
;
}
}
&
.text-center
{
text-align
:
center
;
}
}
.setting-nav
{
...
...
resources/assets/sass/_variables.scss
View file @
ced8c8e
...
...
@@ -38,6 +38,7 @@ $primary-dark: #0288D1;
$secondary
:
#e27b41
;
$positive
:
#52A256
;
$negative
:
#E84F4F
;
$warning
:
$secondary
;
$primary-faded
:
rgba
(
21
,
101
,
192
,
0
.15
);
// Item Colors
...
...
resources/assets/sass/styles.scss
View file @
ced8c8e
...
...
@@ -88,6 +88,10 @@ body.dragging, body.dragging * {
background-color
:
$negative
;
color
:
#EEE
;
}
&
.warning
{
background-color
:
$secondary
;
color
:
#EEE
;
}
}
// Loading icon
...
...
resources/views/pages/create.blade.php
View file @
ced8c8e
...
...
@@ -8,7 +8,7 @@
@section('content')
<div
class=
"flex-fill flex"
ng-non-bindable
>
<div
class=
"flex-fill flex"
>
<form
action=
"{{$book->getUrl() . '/page'}}"
method=
"POST"
class=
"flex flex-fill"
>
@include('pages/form')
@if($chapter)
...
...
resources/views/pages/edit.blade.php
View file @
ced8c8e
...
...
@@ -8,8 +8,8 @@
@section('content')
<div
class=
"flex-fill flex"
ng-non-bindable
>
<form
action=
"{{$page->getUrl()}}"
method=
"POST"
class=
"flex flex-fill"
>
<div
class=
"flex-fill flex"
>
<form
action=
"{{$page->getUrl()}}"
data-page-id=
"{{ $page->id }}"
method=
"POST"
class=
"flex flex-fill"
>
<input
type=
"hidden"
name=
"_method"
value=
"PUT"
>
@include('pages/form', ['model' => $page])
</form>
...
...
resources/views/pages/form.blade.php
View file @
ced8c8e
<div
class=
"page-editor flex-fill flex"
ng-
non-bindable
>
<div
class=
"page-editor flex-fill flex"
ng-
controller=
"PageEditController"
page-id=
"{{ $model->id or 0 }}"
page-draft=
"{{ $page->isDraft or 0 }}"
>
{{ csrf_field() }}
<div
class=
"faded-small toolbar"
>
...
...
@@ -9,12 +9,16 @@
<div
class=
"row"
>
<div
class=
"col-sm-4 faded"
>
<div
class=
"action-buttons text-left"
>
<a
href=
"{{ back()->getTargetUrl() }}"
class=
"text-button text-primary"
><i
class=
"zmdi zmdi-arrow-left"
></i>
Back
</a>
<a
onclick=
"$('body>header').slideToggle();"
class=
"text-button text-primary"
><i
class=
"zmdi zmdi-swap-vertical"
></i>
Toggle Header
</a>
</div>
</div>
<div
class=
"col-sm-8 faded"
>
<div
class=
"action-buttons"
>
<a
href=
"{{ back()->getTargetUrl() }}"
class=
"text-button text-primary"
><i
class=
"zmdi zmdi-close"
></i>
Cancel
</a>
<div
class=
"col-sm-4 faded text-center"
>
<span
class=
"faded-text"
ng-bind=
"draftText"
></span>
</div>
<div
class=
"col-sm-4 faded"
>
<div
class=
"action-buttons"
ng-cloak
>
<button
type=
"button"
ng-if=
"isDraft"
ng-click=
"discardDraft()"
class=
"text-button text-neg"
><i
class=
"zmdi zmdi-close-circle"
></i>
Discard Draft
</button>
<button
type=
"submit"
id=
"save-button"
class=
"text-button text-pos"
><i
class=
"zmdi zmdi-floppy"
></i>
Save Page
</button>
</div>
</div>
...
...
@@ -22,13 +26,13 @@
</div>
</div>
<div
class=
"title-input page-title clearfix"
>
<div
class=
"title-input page-title clearfix"
ng-non-bindable
>
<div
class=
"input"
>
@include('form/text', ['name' => 'name', 'placeholder' => 'Page Title'])
</div>
</div>
<div
class=
"edit-area flex-fill flex"
>
<textarea
id=
"html-editor"
name=
"html"
rows=
"5"
<textarea
id=
"html-editor"
tinymce=
"editorOptions"
mce-change=
"editorChange"
mce-model=
"editorHtml"
name=
"html"
rows=
"5"
@
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'))
<div
class=
"text-neg text-small"
>
{{ $errors->first('html') }}
</div>
...
...
resources/views/pages/revisions.blade.php
View file @
ced8c8e
...
...
@@ -24,10 +24,10 @@
<table
class=
"table"
>
<tr>
<th>
Name
</th>
<th
colspan=
"2"
>
Created By
</th>
<th>
Revision Date
</th>
<th>
Actions
</th>
<th
width=
"40%"
>
Name
</th>
<th
colspan=
"2"
width=
"20%"
>
Created By
</th>
<th
width=
"20%"
>
Revision Date
</th>
<th
width=
"20%"
>
Actions
</th>
</tr>
@foreach($page->revisions as $revision)
<tr>
...
...
@@ -38,7 +38,7 @@
@endif
</td>
<td>
@if($revision->createdBy) {{$revision->createdBy->name}} @else Deleted User @endif
</td>
<td><small>
{{$revision->created_at->format('jS F, Y H:i:s')}} ({{$revision->created_at->diffForHumans()}})
</small></td>
<td><small>
{{$revision->created_at->format('jS F, Y H:i:s')}}
<br>
({{$revision->created_at->diffForHumans()}})
</small></td>
<td>
<a
href=
"{{$revision->getUrl()}}"
target=
"_blank"
>
Preview
</a>
<span
class=
"text-muted"
>
|
</span>
...
...
resources/views/partials/notifications.blade.php
View file @
ced8c8e
<div
class=
"notification anim pos"
@
if
(!
Session::has
('
success
'))
style=
"display:none;"
@
endif
>
<i
class=
"zmdi zmdi-check-circle"
></i>
<span>
{{ Session::get('success') }}
</span>
<i
class=
"zmdi zmdi-check-circle"
></i>
<span>
{!! nl2br(htmlentities(Session::get('success'))) !!}
</span>
</div>
<div
class=
"notification anim warning stopped"
@
if
(!
Session::has
('
warning
'))
style=
"display:none;"
@
endif
>
<i
class=
"zmdi zmdi-info"
></i>
<span>
{!! nl2br(htmlentities(Session::get('warning'))) !!}
</span>
</div>
<div
class=
"notification anim neg stopped"
@
if
(!
Session::has
('
error
'))
style=
"display:none;"
@
endif
>
<i
class=
"zmdi zmdi-alert-circle"
></i>
<span>
{
{ Session::get('error') }
}
</span>
<i
class=
"zmdi zmdi-alert-circle"
></i>
<span>
{
!! nl2br(htmlentities(Session::get('error'))) !!
}
</span>
</div>
...
...
tests/EntitySearchTest.php
→
tests/Entity
/Entity
SearchTest.php
View file @
ced8c8e
File moved
tests/EntityTest.php
→
tests/Entity
/Entity
Test.php
View file @
ced8c8e
File moved
tests/Entity/PageUpdateDraftTest.php
0 → 100644
View file @
ced8c8e
<?php
class
PageUpdateDraftTest
extends
TestCase
{
protected
$page
;
protected
$pageRepo
;
public
function
setUp
()
{
parent
::
setUp
();
$this
->
page
=
\BookStack\Page
::
first
();
$this
->
pageRepo
=
app
(
'\BookStack\Repos\PageRepo'
);
}
public
function
test_draft_content_shows_if_available
()
{
$addedContent
=
'<p>test message content</p>'
;
$this
->
asAdmin
()
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
dontSeeInField
(
'html'
,
$addedContent
);
$newContent
=
$this
->
page
->
html
.
$addedContent
;
$this
->
pageRepo
->
saveUpdateDraft
(
$this
->
page
,
[
'html'
=>
$newContent
]);
$this
->
asAdmin
()
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
seeInField
(
'html'
,
$newContent
);
}
public
function
test_draft_not_visible_by_others
()
{
$addedContent
=
'<p>test message content</p>'
;
$this
->
asAdmin
()
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
dontSeeInField
(
'html'
,
$addedContent
);
$newContent
=
$this
->
page
->
html
.
$addedContent
;
$newUser
=
$this
->
getNewUser
();
$this
->
pageRepo
->
saveUpdateDraft
(
$this
->
page
,
[
'html'
=>
$newContent
]);
$this
->
actingAs
(
$newUser
)
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
dontSeeInField
(
'html'
,
$newContent
);
}
public
function
test_alert_message_shows_if_editing_draft
()
{
$this
->
asAdmin
();
$this
->
pageRepo
->
saveUpdateDraft
(
$this
->
page
,
[
'html'
=>
'test content'
]);
$this
->
asAdmin
()
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
see
(
'You are currently editing a draft'
);
}
public
function
test_alert_message_shows_if_someone_else_editing
()
{
$addedContent
=
'<p>test message content</p>'
;
$this
->
asAdmin
()
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
dontSeeInField
(
'html'
,
$addedContent
);
$newContent
=
$this
->
page
->
html
.
$addedContent
;
$newUser
=
$this
->
getNewUser
();
$this
->
pageRepo
->
saveUpdateDraft
(
$this
->
page
,
[
'html'
=>
$newContent
]);
$this
->
actingAs
(
$newUser
)
->
visit
(
$this
->
page
->
getUrl
()
.
'/edit'
)
->
see
(
'Admin has started editing this page'
);
}
}
tests/RestrictionsTest.php
→
tests/
Permissions/
RestrictionsTest.php
View file @
ced8c8e
File moved
tests/RolesTest.php
→
tests/
Permissions/
RolesTest.php
View file @
ced8c8e
File moved
Please
register
or
sign in
to post a comment