Compare commits

...

43 Commits

Author SHA1 Message Date
Dan Brown
45641d0754 Updated assets for release v0.11.2 2016-08-21 14:56:29 +01:00
Dan Brown
4b1d08ba99 Merge branch 'v0.11' into release 2016-08-21 14:55:11 +01:00
Dan Brown
f8a299caee Fixed login 'intended' redirect for custom urls.
Also changed social account detach wording.
2016-08-21 14:49:40 +01:00
Chris
437dce7756 Applied baseUrl to login redirect 2016-08-21 13:48:56 +01:00
Dan Brown
757f16a3b5 Improved page tag box structure and fixed footer in PDF export.
Fixes #162.
2016-08-21 13:35:22 +01:00
Dan Brown
160fa99ba4 Updated assets for release v0.11.1 2016-08-14 12:40:55 +01:00
Dan Brown
d2a5ab49ed Merge branch 'v0.11' into release 2016-08-14 12:37:48 +01:00
Dan Brown
43d9d2eba7 Updated all application urls to allow path prefix.
Allows BookStack to be installed at a non-root location on a domain.
Closes #40.
2016-08-14 12:29:35 +01:00
Dan Brown
baa260a03d Started work on subdirectory support 2016-08-13 17:56:25 +01:00
Dan Brown
b157a9927a Fixed tag section and editor safari rendering.
Fixes #152.
2016-08-13 14:54:23 +01:00
Dan Brown
b246a67e8a Fixed double brace issues in both editors.
Double braces were being parsed by angular in both the WYSIWYG and markdown editor.
Fixes #150 and fixes #155.
2016-08-13 14:18:31 +01:00
Dan Brown
2d958e88bf Fixed entities created with blank slugs.
Fixes #156.
2016-08-13 13:53:04 +01:00
Dan Brown
07c7d5af17 Updated elixr and fixed table th element borders
Fixes #164.
2016-08-13 10:02:54 +01:00
Dan Brown
c6404d8917 Updated assets for release v0.11 2016-07-03 10:56:16 +01:00
Dan Brown
7113807f12 Merge branch 'master' into release 2016-07-03 10:52:04 +01:00
Dan Brown
10418323ef Prevented shift-tab from selecting a tag 2016-07-03 10:47:20 +01:00
Dan Brown
a11d5245ec Cut down homepage queries a little 2016-07-03 10:42:13 +01:00
Dan Brown
565033e0d4 Fixed bug which hid entities and fixed new chapter priority 2016-07-03 10:31:20 +01:00
Dan Brown
c25ef18900 Made list default messages a little nicer 2016-07-03 10:12:12 +01:00
Dan Brown
b0a63ba0cc Tightened and cleaned some list styles 2016-07-03 09:58:45 +01:00
Dan Brown
7b6c88f17c Fixed error on image deletion
Also Added tests to cover image upload and deletion.
Fixes #136.
2016-07-01 20:13:30 +01:00
Dan Brown
361ba8b244 Made stick book navigation recalc on window resize 2016-06-25 15:46:31 +01:00
Dan Brown
9baa96d41c Added chapter move actions. Closes #86 2016-06-25 15:31:38 +01:00
Dan Brown
e584b4926f Travis CI updates (#130)
* Updated travis config to use mysql

* Added cache for composer
2016-06-19 19:30:43 +01:00
Dan Brown
bcd9c2044e Added WYSIWYG editor callouts 2016-06-19 19:02:53 +01:00
Dan Brown
d582e76fed Fixed theme color elements not showing on new instance
Also cleaned notification session access
2016-06-12 13:37:15 +01:00
Dan Brown
991dd8a558 Merged branch add_page_move into master
References #86
2016-06-12 12:49:08 +01:00
Dan Brown
bc49784797 Added tests to cover page_move features 2016-06-12 12:48:06 +01:00
Dan Brown
7f99903fdb Finished off page move functionality 2016-06-12 12:14:14 +01:00
Dan Brown
97d011ac8e Started work on page move view and entity selector 2016-06-11 21:04:18 +01:00
Dan Brown
61596a8e21 Prevent page redirect on draft saving
Only an issue when using the non 'mysqlnd' extension.
Fixes #120
2016-06-10 19:45:53 +01:00
Dan Brown
44e337cef6 Merged branch rovox-master into master 2016-06-04 16:33:23 +01:00
Dan Brown
1bec3eaa1e Added checks to use MyISAM if MySQL 5.5 is found 2016-06-04 16:32:57 +01:00
Dan Brown
5942d796b5 Merge branch 'master' of git://github.com/rovox/BookStack into rovox-master 2016-06-04 15:43:08 +01:00
Dan Brown
eec9c05518 Added tag autosuggestion when no input provided
Shows the most popular tag names/values.
As requested on #121
2016-06-04 15:37:28 +01:00
Dan Brown
246d1621f5 Limited tag value autosuggestions based on tag name
As requested on #121
2016-06-04 14:54:31 +01:00
Dan Brown
6c1e06bf86 Updated License file 2016-06-03 18:52:49 +01:00
Dan Brown
3c1e165134 Add in LICENSE file 2016-06-03 18:51:47 +01:00
Dan Brown
80d1c594cc Added licence and release badges 2016-06-03 18:50:20 +01:00
Dan Brown
947db95d16 Fixed error with similar activity filtering 2016-05-28 14:02:48 +01:00
Dan Brown
5b9362ab0b Added (Ctrl+s) draft force save 2016-05-28 13:51:07 +01:00
Dan Brown
f602b088ac Added basic markdown scroll syncing 2016-05-28 13:24:07 +01:00
robert
4acf0c4ee0 Making sure MyISAM is set for the tables that need it for new installtions that are using mariadb. 2016-05-25 23:52:43 +02:00
130 changed files with 1636 additions and 650 deletions

View File

@@ -3,6 +3,10 @@ APP_ENV=production
APP_DEBUG=false
APP_KEY=SomeRandomString
# The below url has to be set if using social auth options
# or if you are not using BookStack at the root path of your domain.
# APP_URL=http://bookstack.dev
# Database details
DB_HOST=localhost
DB_DATABASE=database_database
@@ -42,8 +46,6 @@ GITHUB_APP_ID=false
GITHUB_APP_SECRET=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
# URL used for social login redirects, NO TRAILING SLASH
APP_URL=http://bookstack.dev
# External services such as Gravatar
DISABLE_EXTERNAL_SERVICES=false

View File

@@ -1,3 +1,5 @@
dist: trusty
sudo: required
language: php
php:
- 7.0
@@ -5,15 +7,21 @@ php:
cache:
directories:
- vendor
- node_modules
- $HOME/.composer/cache
addons:
mariadb: '10.0'
apt:
packages:
- mysql-server-5.6
- mysql-client-core-5.6
- mysql-client-5.6
before_install:
- npm install -g npm@latest
before_script:
- mysql -e 'create database `bookstack-test`;'
- mysql -u root -e 'create database `bookstack-test`;'
- composer config -g github-oauth.github.com $GITHUB_ACCESS_TOKEN
- phpenv config-rm xdebug.ini
- composer self-update

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Dan Brown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -44,7 +44,7 @@ class Activity extends Model
* @return bool
*/
public function isSimilarTo($activityB) {
return [$this->key, $this->entitiy_type, $this->entitiy_id] === [$activityB->key, $activityB->entitiy_type, $activityB->entitiy_id];
return [$this->key, $this->entity_type, $this->entity_id] === [$activityB->key, $activityB->entity_type, $activityB->entity_id];
}
}

View File

@@ -7,11 +7,15 @@ class Book extends Entity
/**
* Get the url for this book.
* @param string|bool $path
* @return string
*/
public function getUrl()
public function getUrl($path = false)
{
return '/books/' . $this->slug;
if ($path !== false) {
return baseUrl('/books/' . $this->slug . '/' . trim($path, '/'));
}
return baseUrl('/books/' . $this->slug);
}
/*

View File

@@ -25,12 +25,16 @@ class Chapter extends Entity
/**
* Get the url of this chapter.
* @param string|bool $path
* @return string
*/
public function getUrl()
public function getUrl($path = false)
{
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
return '/books/' . $bookSlug. '/chapter/' . $this->slug;
if ($path !== false) {
return baseUrl('/books/' . $bookSlug. '/chapter/' . $this->slug . '/' . trim($path, '/'));
}
return baseUrl('/books/' . $bookSlug. '/chapter/' . $this->slug);
}
/**

View File

@@ -48,8 +48,8 @@ class Handler extends ExceptionHandler
// Handle notify exceptions which will redirect to the
// specified location then show a notification message.
if ($e instanceof NotifyException) {
\Session::flash('error', $e->message);
return response()->redirectTo($e->redirectLocation);
session()->flash('error', $e->message);
return redirect($e->redirectLocation);
}
// Handle pretty exceptions which will show a friendly application-fitting page

View File

@@ -1,9 +1,6 @@
<?php
namespace BookStack\Http\Controllers\Auth;
<?php namespace BookStack\Http\Controllers\Auth;
use BookStack\Exceptions\AuthException;
use BookStack\Exceptions\PrettyException;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use BookStack\Exceptions\SocialSignInException;
@@ -36,7 +33,6 @@ class AuthController extends Controller
protected $redirectAfterLogout = '/login';
protected $username = 'email';
protected $socialAuthService;
protected $emailConfirmationService;
protected $userRepo;
@@ -53,6 +49,8 @@ class AuthController extends Controller
$this->socialAuthService = $socialAuthService;
$this->emailConfirmationService = $emailConfirmationService;
$this->userRepo = $userRepo;
$this->redirectPath = baseUrl('/');
$this->redirectAfterLogout = baseUrl('/login');
$this->username = config('auth.method') === 'standard' ? 'email' : 'username';
parent::__construct();
}
@@ -147,7 +145,9 @@ class AuthController extends Controller
auth()->login($user);
}
return redirect()->intended($this->redirectPath());
$path = session()->pull('url.intended', '/');
$path = baseUrl($path, true);
return redirect($path);
}
/**

View File

@@ -59,7 +59,7 @@ class ChapterController extends Controller
$input = $request->all();
$input['priority'] = $this->bookRepo->getNewPriority($book);
$chapter = $this->chapterRepo->createFromInput($request->all(), $book);
$chapter = $this->chapterRepo->createFromInput($input, $book);
Activity::add($chapter, 'chapter_create', $book->id);
return redirect($chapter->getUrl());
}
@@ -154,6 +154,63 @@ 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
]);
}
/**
* Perform the move action for a chapter.
* @param $bookSlug
* @param $chapterSlug
* @param Request $request
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
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

View File

@@ -51,9 +51,9 @@ class ImageController extends Controller
$this->validate($request, [
'term' => 'required|string'
]);
$searchTerm = $request->get('term');
$imgData = $this->imageRepo->searchPaginatedByType($type, $page,24, $searchTerm);
$imgData = $this->imageRepo->searchPaginatedByType($type, $page, 24, $searchTerm);
return response()->json($imgData);
}
@@ -99,7 +99,7 @@ class ImageController extends Controller
{
$this->checkPermission('image-create-all');
$this->validate($request, [
'file' => 'image|mimes:jpeg,gif,png'
'file' => 'is_image'
]);
$imageUpload = $request->file('file');

View File

@@ -92,7 +92,7 @@ class PageController extends Controller
$draftPage = $this->pageRepo->getById($pageId, true);
$chapterId = $draftPage->chapter_id;
$chapterId = intval($draftPage->chapter_id);
$parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book;
$this->checkOwnablePermission('page-create', $parent);
@@ -221,8 +221,8 @@ class PageController extends Controller
$updateTime = $draft->updated_at->timestamp;
$utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset;
return response()->json([
'status' => 'success',
'message' => 'Draft saved at ',
'status' => 'success',
'message' => 'Draft saved at ',
'timestamp' => $utcUpdateTimestamp
]);
}
@@ -412,7 +412,7 @@ class PageController extends Controller
*/
public function showRecentlyCreated()
{
$pages = $this->pageRepo->getRecentlyCreatedPaginated(20);
$pages = $this->pageRepo->getRecentlyCreatedPaginated(20)->setPath(baseUrl('/pages/recently-created'));
return view('pages/detailed-listing', [
'title' => 'Recently Created Pages',
'pages' => $pages
@@ -425,7 +425,7 @@ class PageController extends Controller
*/
public function showRecentlyUpdated()
{
$pages = $this->pageRepo->getRecentlyUpdatedPaginated(20);
$pages = $this->pageRepo->getRecentlyUpdatedPaginated(20)->setPath(baseUrl('/pages/recently-updated'));
return view('pages/detailed-listing', [
'title' => 'Recently Updated Pages',
'pages' => $pages
@@ -450,6 +450,67 @@ class PageController extends Controller
]);
}
/**
* Show the view to choose a new parent to move a page into.
* @param $bookSlug
* @param $pageSlug
* @return mixed
* @throws NotFoundException
*/
public function showMove($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->checkOwnablePermission('page-update', $page);
return view('pages/move', [
'book' => $book,
'page' => $page
]);
}
/**
* 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);
$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

View File

@@ -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();
}
@@ -48,9 +51,9 @@ class SearchController extends Controller
$chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends);
$this->setPageTitle('Search For ' . $searchTerm);
return view('search/all', [
'pages' => $pages,
'books' => $books,
'chapters' => $chapters,
'pages' => $pages,
'books' => $books,
'chapters' => $chapters,
'searchTerm' => $searchTerm
]);
}
@@ -69,8 +72,8 @@ class SearchController extends Controller
$pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
$this->setPageTitle('Page Search For ' . $searchTerm);
return view('search/entity-search-list', [
'entities' => $pages,
'title' => 'Page Search Results',
'entities' => $pages,
'title' => 'Page Search Results',
'searchTerm' => $searchTerm
]);
}
@@ -89,8 +92,8 @@ class SearchController extends Controller
$chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
$this->setPageTitle('Chapter Search For ' . $searchTerm);
return view('search/entity-search-list', [
'entities' => $chapters,
'title' => 'Chapter Search Results',
'entities' => $chapters,
'title' => 'Chapter Search Results',
'searchTerm' => $searchTerm
]);
}
@@ -109,8 +112,8 @@ class SearchController extends Controller
$books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends);
$this->setPageTitle('Book Search For ' . $searchTerm);
return view('search/entity-search-list', [
'entities' => $books,
'title' => 'Book Search Results',
'entities' => $books,
'title' => 'Book Search Results',
'searchTerm' => $searchTerm
]);
}
@@ -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('search/entity-ajax-list', ['entities' => $entities]);
}
}

View File

@@ -55,7 +55,7 @@ class TagController extends Controller
*/
public function getNameSuggestions(Request $request)
{
$searchTerm = $request->get('search');
$searchTerm = $request->has('search') ? $request->get('search') : false;
$suggestions = $this->tagRepo->getNameSuggestions($searchTerm);
return response()->json($suggestions);
}
@@ -66,8 +66,9 @@ class TagController extends Controller
*/
public function getValueSuggestions(Request $request)
{
$searchTerm = $request->get('search');
$suggestions = $this->tagRepo->getValueSuggestions($searchTerm);
$searchTerm = $request->has('search') ? $request->get('search') : false;
$tagName = $request->has('name') ? $request->get('name') : false;
$suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName);
return response()->json($suggestions);
}

View File

@@ -33,14 +33,14 @@ class Authenticate
public function handle($request, Closure $next)
{
if ($this->auth->check() && setting('registration-confirmation') && !$this->auth->user()->email_confirmed) {
return redirect()->guest('/register/confirm/awaiting');
return redirect()->guest(baseUrl('/register/confirm/awaiting'));
}
if ($this->auth->guest() && !setting('app-public')) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('/login');
return redirect()->guest(baseUrl('/login'));
}
}

View File

@@ -34,6 +34,8 @@ Route::group(['middleware' => 'auth'], function () {
Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml');
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');
@@ -53,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');
@@ -93,6 +97,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');

View File

@@ -56,14 +56,20 @@ class Page extends Entity
/**
* Get the url for this page.
* @param string|bool $path
* @return string
*/
public function getUrl()
public function getUrl($path = false)
{
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
$midText = $this->draft ? '/draft/' : '/page/';
$idComponent = $this->draft ? $this->id : $this->slug;
return '/books/' . $bookSlug . $midText . $idComponent;
if ($path !== false) {
return baseUrl('/books/' . $bookSlug . $midText . $idComponent . '/' . trim($path, '/'));
}
return baseUrl('/books/' . $bookSlug . $midText . $idComponent);
}
/**

View File

@@ -15,7 +15,12 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
// Custom validation methods
\Validator::extend('is_image', function($attribute, $value, $parameters, $validator) {
$imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
return in_array($value->getMimeType(), $imageMimes);
});
}
/**

View File

@@ -0,0 +1,30 @@
<?php namespace BookStack\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
class PaginationServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
Paginator::currentPathResolver(function () {
return baseUrl($this->app['request']->path());
});
Paginator::currentPageResolver(function ($pageName = 'page') {
$page = $this->app['request']->input($pageName);
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
return $page;
}
return 1;
});
}
}

View File

@@ -216,12 +216,10 @@ class BookRepo extends EntityRepo
*/
public function findSuitableSlug($name, $currentId = false)
{
$originalSlug = Str::slug($name);
$slug = $originalSlug;
$count = 2;
$slug = Str::slug($name);
if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
while ($this->doesSlugExist($slug, $currentId)) {
$slug = $originalSlug . '-' . $count;
$count++;
$slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
}
return $slug;
}
@@ -229,7 +227,7 @@ class BookRepo extends EntityRepo
/**
* Get all child objects of a book.
* Returns a sorted collection of Pages and Chapters.
* Loads the bookslug onto child elements to prevent access database access for getting the slug.
* Loads the book slug onto child elements to prevent access database access for getting the slug.
* @param Book $book
* @param bool $filterDrafts
* @return mixed
@@ -245,13 +243,16 @@ class BookRepo extends EntityRepo
$pages = $pageQuery->get();
$chapterQuery = $book->chapters()->with(['pages' => function($query) use ($filterDrafts) {
$chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) {
$this->permissionService->enforcePageRestrictions($query, 'view');
if ($filterDrafts) $query->where('draft', '=', false);
}]);
$chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view');
$chapters = $chapterQuery->get();
$children = $pages->merge($chapters);
$children = $pages->values();
foreach ($chapters as $chapter) {
$children->push($chapter);
}
$bookSlug = $book->slug;
$children->each(function ($child) use ($bookSlug) {
@@ -260,7 +261,7 @@ class BookRepo extends EntityRepo
$child->pages->each(function ($page) use ($bookSlug) {
$page->setAttribute('bookSlug', $bookSlug);
});
$child->pages = $child->pages->sortBy(function($child, $key) {
$child->pages = $child->pages->sortBy(function ($child, $key) {
$score = $child->priority;
if ($child->draft) $score -= 100;
return $score;
@@ -269,7 +270,7 @@ class BookRepo extends EntityRepo
});
// Sort items with drafts first then by priority.
return $children->sortBy(function($child, $key) {
return $children->sortBy(function ($child, $key) {
$score = $child->priority;
if ($child->isA('page') && $child->draft) $score -= 100;
return $score;

View File

@@ -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
@@ -69,7 +81,7 @@ class ChapterRepo extends EntityRepo
{
$pages = $this->permissionService->enforcePageRestrictions($chapter->pages())->get();
// Sort items with drafts first then by priority.
return $pages->sortBy(function($child, $key) {
return $pages->sortBy(function ($child, $key) {
$score = $child->priority;
if ($child->draft) $score -= 100;
return $score;
@@ -139,6 +151,7 @@ class ChapterRepo extends EntityRepo
public function findSuitableSlug($name, $bookId, $currentId = false)
{
$slug = Str::slug($name);
if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
while ($this->doesSlugExist($slug, $bookId, $currentId)) {
$slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
}
@@ -189,12 +202,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;
}

View File

@@ -13,7 +13,7 @@ class ImageRepo
protected $image;
protected $imageService;
protected $restictionService;
protected $restrictionService;
protected $page;
/**
@@ -27,7 +27,7 @@ class ImageRepo
{
$this->image = $image;
$this->imageService = $imageService;
$this->restictionService = $permissionService;
$this->restrictionService = $permissionService;
$this->page = $page;
}
@@ -52,7 +52,7 @@ class ImageRepo
*/
private function returnPaginated($query, $page = 0, $pageSize = 24)
{
$images = $this->restictionService->filterRelatedPages($query, 'images', 'uploaded_to');
$images = $this->restrictionService->filterRelatedPages($query, 'images', 'uploaded_to');
$images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
$hasMore = count($images) > $pageSize;

View File

@@ -3,6 +3,7 @@
use Activity;
use BookStack\Book;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
use Carbon\Carbon;
use DOMDocument;
@@ -146,7 +147,7 @@ class PageRepo extends EntityRepo
$draftPage->fill($input);
// Save page tags if present
if(isset($input['tags'])) {
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
}
@@ -318,7 +319,7 @@ class PageRepo extends EntityRepo
}
// Save page tags if present
if(isset($input['tags'])) {
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($page, $input['tags']);
}
@@ -404,7 +405,7 @@ class PageRepo extends EntityRepo
$draft->fill($data);
if (setting('app-editor') !== 'markdown') $draft->markdown = '';
$draft->save();
return $draft;
}
@@ -572,16 +573,33 @@ 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
* @param $bookId
* @param string $name
* @param int $bookId
* @param bool|false $currentId
* @return string
*/
public function findSuitableSlug($name, $bookId, $currentId = false)
{
$slug = Str::slug($name);
if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
while ($this->doesSlugExist($slug, $bookId, $currentId)) {
$slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
}

View File

@@ -58,29 +58,48 @@ class TagRepo
/**
* Get tag name suggestions from scanning existing tag names.
* If no search term is given the 50 most popular tag names are provided.
* @param $searchTerm
* @return array
*/
public function getNameSuggestions($searchTerm)
public function getNameSuggestions($searchTerm = false)
{
if ($searchTerm === '') return [];
$query = $this->tag->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc');
$query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('name');
if ($searchTerm) {
$query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc');
} else {
$query = $query->orderBy('count', 'desc')->take(50);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name');
}
/**
* Get tag value suggestions from scanning existing tag values.
* If no search is given the 50 most popular values are provided.
* Passing a tagName will only find values for a tags with a particular name.
* @param $searchTerm
* @param $tagName
* @return array
*/
public function getValueSuggestions($searchTerm)
public function getValueSuggestions($searchTerm = false, $tagName = false)
{
if ($searchTerm === '') return [];
$query = $this->tag->where('value', 'LIKE', $searchTerm . '%')->groupBy('value')->orderBy('value', 'desc');
$query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('value');
if ($searchTerm) {
$query = $query->where('value', 'LIKE', $searchTerm . '%')->orderBy('value', 'desc');
} else {
$query = $query->orderBy('count', 'desc')->take(50);
}
if ($tagName !== false) $query = $query->where('name', '=', $tagName);
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value');
}
/**
* Save an array of tags to an entity
* @param Entity $entity

View File

@@ -90,7 +90,7 @@ class ActivityService
{
$activityList = $this->permissionService
->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get();
->orderBy('created_at', 'desc')->with('user', 'entity')->skip($count * $page)->take($count)->get();
return $this->filterSimilar($activityList);
}

View File

@@ -265,7 +265,7 @@ class ImageService
$this->storageUrl = $storageUrl;
}
return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
return ($this->storageUrl == false ? rtrim(baseUrl(''), '/') : rtrim($this->storageUrl, '/')) . $filePath;
}

View File

@@ -4,6 +4,7 @@ use BookStack\Book;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\JointPermission;
use BookStack\Ownable;
use BookStack\Page;
use BookStack\Role;
use BookStack\User;
@@ -307,16 +308,16 @@ class PermissionService
/**
* Checks if an entity has a restriction set upon it.
* @param Entity $entity
* @param Ownable $ownable
* @param $permission
* @return bool
*/
public function checkEntityUserAccess(Entity $entity, $permission)
public function checkOwnableUserAccess(Ownable $ownable, $permission)
{
if ($this->isAdmin) return true;
$explodedPermission = explode('-', $permission);
$baseQuery = $entity->where('id', '=', $entity->id);
$baseQuery = $ownable->where('id', '=', $ownable->id);
$action = end($explodedPermission);
$this->currentAction = $action;
@@ -327,7 +328,7 @@ class PermissionService
$allPermission = $this->currentUser && $this->currentUser->can($permission . '-all');
$ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own');
$this->currentAction = 'view';
$isOwner = $this->currentUser && $this->currentUser->id === $entity->created_by;
$isOwner = $this->currentUser && $this->currentUser->id === $ownable->created_by;
return ($allPermission || ($isOwner && $ownPermission));
}

View File

@@ -113,20 +113,20 @@ class SocialAuthService
if ($isLoggedIn && $socialAccount === null) {
$this->fillSocialAccount($socialDriver, $socialUser);
$currentUser->socialAccounts()->save($this->socialAccount);
\Session::flash('success', title_case($socialDriver) . ' account was successfully attached to your profile.');
session()->flash('success', title_case($socialDriver) . ' account was successfully attached to your profile.');
return redirect($currentUser->getEditUrl());
}
// When a user is logged in and the social account exists and is already linked to the current user.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
\Session::flash('error', 'This ' . title_case($socialDriver) . ' account is already attached to your profile.');
session()->flash('error', 'This ' . title_case($socialDriver) . ' account is already attached to your profile.');
return redirect($currentUser->getEditUrl());
}
// When a user is logged in, A social account exists but the users do not match.
// Change the user that the social account is assigned to.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
\Session::flash('success', 'This ' . title_case($socialDriver) . ' account is already used by another user.');
session()->flash('success', 'This ' . title_case($socialDriver) . ' account is already used by another user.');
return redirect($currentUser->getEditUrl());
}
@@ -135,6 +135,7 @@ class SocialAuthService
if (setting('registration-enabled')) {
$message .= ' or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option';
}
throw new SocialSignInException($message . '.', '/login');
}
@@ -214,7 +215,7 @@ class SocialAuthService
{
session();
auth()->user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
\Session::flash('success', $socialDriver . ' account successfully detached');
session()->flash('success', title_case($socialDriver) . ' account successfully detached');
return redirect(auth()->user()->getEditUrl());
}

View File

@@ -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');
}

View File

@@ -138,8 +138,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function getAvatar($size = 50)
{
if ($this->image_id === 0 || $this->image_id === '0' || $this->image_id === null) return '/user_avatar.png';
return $this->avatar->getThumb($size, $size, false);
if ($this->image_id === 0 || $this->image_id === '0' || $this->image_id === null) return baseUrl('/user_avatar.png');
return baseUrl($this->avatar->getThumb($size, $size, false));
}
/**
@@ -157,7 +157,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function getEditUrl()
{
return '/settings/users/' . $this->id;
return baseUrl('/settings/users/' . $this->id);
}
/**
* Get the url that links to this user's profile.
* @return mixed
*/
public function getProfileUrl()
{
return baseUrl('/user/' . $this->id);
}
/**

View File

@@ -1,5 +1,7 @@
<?php
use BookStack\Ownable;
if (!function_exists('versioned_asset')) {
/**
* Get the path to a versioned file.
@@ -18,11 +20,11 @@ if (!function_exists('versioned_asset')) {
}
if (isset($manifest[$file])) {
return '/' . $manifest[$file];
return baseUrl($manifest[$file]);
}
if (file_exists(public_path($file))) {
return '/' . $file;
return baseUrl($file);
}
throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
@@ -34,18 +36,18 @@ if (!function_exists('versioned_asset')) {
* If an ownable element is passed in the jointPermissions are checked against
* that particular item.
* @param $permission
* @param \BookStack\Ownable $ownable
* @param Ownable $ownable
* @return mixed
*/
function userCan($permission, \BookStack\Ownable $ownable = null)
function userCan($permission, Ownable $ownable = null)
{
if ($ownable === null) {
return auth()->user() && auth()->user()->can($permission);
}
// Check permission on ownable item
$permissionService = app('BookStack\Services\PermissionService');
return $permissionService->checkEntityUserAccess($ownable, $permission);
$permissionService = app(\BookStack\Services\PermissionService::class);
return $permissionService->checkOwnableUserAccess($ownable, $permission);
}
/**
@@ -60,6 +62,48 @@ function setting($key, $default = false)
return $settingService->get($key, $default);
}
/**
* Helper to create url's relative to the applications root path.
* @param string $path
* @param bool $forceAppDomain
* @return string
*/
function baseUrl($path, $forceAppDomain = false)
{
$isFullUrl = strpos($path, 'http') === 0;
if ($isFullUrl && !$forceAppDomain) return $path;
$path = trim($path, '/');
if ($isFullUrl && $forceAppDomain) {
$explodedPath = explode('/', $path);
$path = implode('/', array_splice($explodedPath, 3));
}
return rtrim(config('app.url'), '/') . '/' . $path;
}
/**
* Get an instance of the redirector.
* Overrides the default laravel redirect helper.
* Ensures it redirects even when the app is in a subdirectory.
*
* @param string|null $to
* @param int $status
* @param array $headers
* @param bool $secure
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
function redirect($to = null, $status = 302, $headers = [], $secure = null)
{
if (is_null($to)) {
return app('redirect');
}
$to = baseUrl($to);
return app('redirect')->to($to, $status, $headers, $secure);
}
/**
* Generate a url with multiple parameters for sorting purposes.
* Works out the logic to set the correct sorting direction
@@ -89,5 +133,5 @@ function sortUrl($path, $data, $overrideData = [])
if (count($queryStringSections) === 0) return $path;
return $path . '?' . implode('&', $queryStringSections);
return baseUrl($path . '?' . implode('&', $queryStringSections));
}

View File

@@ -14,6 +14,7 @@ define('LARAVEL_START', microtime(true));
|
*/
require __DIR__.'/../app/helpers.php';
require __DIR__.'/../vendor/autoload.php';
/*

View File

@@ -29,10 +29,7 @@
],
"psr-4": {
"BookStack\\": "app/"
},
"files": [
"app/helpers.php"
]
}
},
"autoload-dev": {
"classmap": [

View File

@@ -31,7 +31,7 @@ return [
|
*/
'url' => env('APP_URL', 'http://localhost'),
'url' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
/*
|--------------------------------------------------------------------------
@@ -130,7 +130,6 @@ return [
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
@@ -153,6 +152,8 @@ return [
/*
* Application Service Providers...
*/
BookStack\Providers\PaginationServiceProvider::class,
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class,
BookStack\Providers\EventServiceProvider::class,

View File

@@ -5,6 +5,9 @@
*/
return [
'app-editor' => 'wysiwyg'
'app-name' => 'BookStack',
'app-editor' => 'wysiwyg',
'app-color' => '#0288D1',
'app-color-light' => 'rgba(21, 101, 192, 0.15)'
];

View File

@@ -12,7 +12,13 @@ class CreateBooksTable extends Migration
*/
public function up()
{
Schema::create('books', function (Blueprint $table) {
$pdo = \DB::connection()->getPdo();
$mysqlVersion = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
$requiresISAM = strpos($mysqlVersion, '5.5') === 0;
Schema::create('books', function (Blueprint $table) use ($requiresISAM) {
if($requiresISAM) $table->engine = 'MyISAM';
$table->increments('id');
$table->string('name');
$table->string('slug')->indexed();

View File

@@ -12,7 +12,13 @@ class CreatePagesTable extends Migration
*/
public function up()
{
Schema::create('pages', function (Blueprint $table) {
$pdo = \DB::connection()->getPdo();
$mysqlVersion = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
$requiresISAM = strpos($mysqlVersion, '5.5') === 0;
Schema::create('pages', function (Blueprint $table) use ($requiresISAM) {
if($requiresISAM) $table->engine = 'MyISAM';
$table->increments('id');
$table->integer('book_id');
$table->integer('chapter_id');

View File

@@ -12,7 +12,12 @@ class CreateChaptersTable extends Migration
*/
public function up()
{
Schema::create('chapters', function (Blueprint $table) {
$pdo = \DB::connection()->getPdo();
$mysqlVersion = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
$requiresISAM = strpos($mysqlVersion, '5.5') === 0;
Schema::create('chapters', function (Blueprint $table) use ($requiresISAM) {
if($requiresISAM) $table->engine = 'MyISAM';
$table->increments('id');
$table->integer('book_id');
$table->string('slug')->indexed();

View File

@@ -12,7 +12,7 @@
"babel-runtime": "^5.8.29",
"bootstrap-sass": "^3.0.0",
"dropzone": "^4.0.1",
"laravel-elixir": "^3.4.0",
"laravel-elixir": "^5.0.0",
"marked": "^0.3.5",
"moment": "^2.12.0",
"zeroclipboard": "^2.2.0"

View File

@@ -1,5 +1,5 @@
{
"css/styles.css": "css/styles.css?version=8cf4d52",
"css/print-styles.css": "css/print-styles.css?version=8cf4d52",
"js/common.js": "js/common.js?version=8cf4d52"
"css/styles.css": "css/styles.css?version=d6312b2",
"css/print-styles.css": "css/print-styles.css?version=d6312b2",
"js/common.js": "js/common.js?version=d6312b2"
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
.faded-small,.print-hidden,header{display:none}body{font-size:12px}.page-content{margin:0 auto}.print-full-width{width:100%;float:none;display:block}h2{font-size:2em;line-height:1;margin-top:.6em;margin-bottom:.3em}
header{display:none}body{font-size:12px}.faded-small{display:none}.page-content{margin:0 auto}.print-hidden{display:none}.print-full-width{width:100%;float:none;display:block}h2{font-size:2em;line-height:1;margin-top:.6em;margin-bottom:.3em}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,7 @@
# BookStack
[![GitHub release](https://img.shields.io/github/release/ssddanbrown/BookStack.svg?maxAge=2592000)](https://github.com/ssddanbrown/BookStack/releases/latest)
[![license](https://img.shields.io/github/license/ssddanbrown/BookStack.svg?maxAge=2592000)](https://github.com/ssddanbrown/BookStack/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/ssddanbrown/BookStack.svg)](https://travis-ci.org/ssddanbrown/BookStack)
A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
@@ -48,3 +50,4 @@ These are the great projects used to help build BookStack:
* [ZeroClipboard](http://zeroclipboard.org/)
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
* [Marked](https://github.com/chjj/marked)
* [Moment.js](http://momentjs.com/)

View File

@@ -1,6 +1,6 @@
"use strict";
var moment = require('moment');
const moment = require('moment');
module.exports = function (ngApp, events) {
@@ -35,7 +35,7 @@ module.exports = function (ngApp, events) {
* @returns {string}
*/
$scope.getUploadUrl = function () {
return '/images/' + $scope.imageType + '/upload';
return window.baseUrl('/images/' + $scope.imageType + '/upload');
};
/**
@@ -133,7 +133,7 @@ module.exports = function (ngApp, events) {
$scope.showing = false;
};
var baseUrl = '/images/' + $scope.imageType + '/all/'
var baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/');
/**
* Fetch the list image data from the server.
@@ -178,7 +178,7 @@ module.exports = function (ngApp, events) {
$scope.images = [];
$scope.hasMore = false;
page = 0;
baseUrl = '/images/' + $scope.imageType + '/search/';
baseUrl = window.baseUrl('/images/' + $scope.imageType + '/search/');
fetchData();
};
@@ -192,7 +192,7 @@ module.exports = function (ngApp, events) {
$scope.hasMore = false;
page = 0;
$scope.view = viewName;
baseUrl = '/images/' + $scope.imageType + '/' + viewName + '/';
baseUrl = window.baseUrl('/images/' + $scope.imageType + '/' + viewName + '/');
fetchData();
}
@@ -202,7 +202,7 @@ module.exports = function (ngApp, events) {
*/
$scope.saveImageDetails = function (event) {
event.preventDefault();
var url = '/images/update/' + $scope.selectedImage.id;
var url = window.baseUrl('/images/update/' + $scope.selectedImage.id);
$http.put(url, this.selectedImage).then((response) => {
events.emit('success', 'Image details updated');
}, (response) => {
@@ -228,7 +228,7 @@ module.exports = function (ngApp, events) {
$scope.deleteImage = function (event) {
event.preventDefault();
var force = $scope.dependantPages !== false;
var url = '/images/' + $scope.selectedImage.id;
var url = window.baseUrl('/images/' + $scope.selectedImage.id);
if (force) url += '?force=true';
$http.delete(url).then((response) => {
$scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
@@ -267,7 +267,7 @@ module.exports = function (ngApp, events) {
if (term.length == 0) return;
$scope.searching = true;
$scope.searchResults = '';
var searchUrl = '/search/book/' + $attrs.bookId;
var searchUrl = window.baseUrl('/search/book/' + $attrs.bookId);
searchUrl += '?term=' + encodeURIComponent(term);
$http.get(searchUrl).then((response) => {
$scope.searchResults = $sce.trustAsHtml(response.data);
@@ -368,7 +368,8 @@ module.exports = function (ngApp, events) {
if (isMarkdown) data.markdown = $scope.editContent;
$http.put('/ajax/page/' + pageId + '/save-draft', data).then((responseData) => {
let url = window.baseUrl('/ajax/page/' + pageId + '/save-draft');
$http.put(url, data).then((responseData) => {
var updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
$scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
@@ -379,12 +380,22 @@ module.exports = function (ngApp, events) {
saveDraft();
};
// Listen to shortcuts coming via events
$scope.$on('editor-keydown', (event, data) => {
// Save shortcut (ctrl+s)
if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) {
data.preventDefault();
saveDraft();
}
});
/**
* 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) => {
let url = window.baseUrl('/ajax/page/' + pageId);
$http.get(url).then((responseData) => {
if (autoSave) $interval.cancel(autoSave);
$scope.draftText = 'Editing Page';
$scope.isUpdateDraft = false;
@@ -428,7 +439,8 @@ module.exports = function (ngApp, events) {
* Get all tags for the current book and add into scope.
*/
function getTags() {
$http.get('/ajax/tags/get/page/' + pageId).then((responseData) => {
let url = window.baseUrl('/ajax/tags/get/page/' + pageId);
$http.get(url).then((responseData) => {
$scope.tags = responseData.data;
addEmptyTag();
});
@@ -477,7 +489,8 @@ module.exports = function (ngApp, events) {
$scope.saveTags = function() {
setTagOrder();
let postData = {tags: $scope.tags};
$http.post('/ajax/tags/update/page/' + pageId, postData).then((responseData) => {
let url = window.baseUrl('/ajax/tags/update/page/' + pageId);
$http.post(url, postData).then((responseData) => {
$scope.tags = responseData.data.tags;
addEmptyTag();
events.emit('success', responseData.data.message);

View File

@@ -1,10 +1,10 @@
"use strict";
var DropZone = require('dropzone');
var markdown = require('marked');
const DropZone = require('dropzone');
const markdown = require('marked');
var toggleSwitchTemplate = require('./components/toggle-switch.html');
var imagePickerTemplate = require('./components/image-picker.html');
var dropZoneTemplate = require('./components/drop-zone.html');
const toggleSwitchTemplate = require('./components/toggle-switch.html');
const imagePickerTemplate = require('./components/image-picker.html');
const dropZoneTemplate = require('./components/drop-zone.html');
module.exports = function (ngApp, events) {
@@ -54,7 +54,7 @@ module.exports = function (ngApp, events) {
imageClass: '@'
},
link: function (scope, element, attrs) {
var usingIds = typeof scope.currentId !== 'undefined' || scope.currentId === 'false';
let usingIds = typeof scope.currentId !== 'undefined' || scope.currentId === 'false';
scope.image = scope.currentImage;
scope.value = scope.currentImage || '';
if (usingIds) scope.value = scope.currentId;
@@ -80,7 +80,7 @@ module.exports = function (ngApp, events) {
};
scope.updateImageFromModel = function (model) {
var isResized = scope.resizeWidth && scope.resizeHeight;
let isResized = scope.resizeWidth && scope.resizeHeight;
if (!isResized) {
scope.$apply(() => {
@@ -89,8 +89,9 @@ module.exports = function (ngApp, events) {
return;
}
var cropped = scope.resizeCrop ? 'true' : 'false';
var requestString = '/images/thumb/' + model.id + '/' + scope.resizeWidth + '/' + scope.resizeHeight + '/' + cropped;
let cropped = scope.resizeCrop ? 'true' : 'false';
let requestString = '/images/thumb/' + model.id + '/' + scope.resizeWidth + '/' + scope.resizeHeight + '/' + cropped;
requestString = window.baseUrl(requestString);
$http.get(requestString).then((response) => {
setImage(model, response.data.url);
});
@@ -149,7 +150,10 @@ module.exports = function (ngApp, events) {
};
}]);
/**
* Dropdown
* Provides some simple logic to create small dropdown menus
*/
ngApp.directive('dropdown', [function () {
return {
restrict: 'A',
@@ -166,7 +170,11 @@ module.exports = function (ngApp, events) {
};
}]);
ngApp.directive('tinymce', ['$timeout', function($timeout) {
/**
* TinyMCE
* An angular wrapper around the tinyMCE editor.
*/
ngApp.directive('tinymce', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: {
@@ -185,6 +193,10 @@ module.exports = function (ngApp, events) {
scope.mceChange(content);
});
editor.on('keydown', (event) => {
scope.$emit('editor-keydown', event);
});
editor.on('init', (e) => {
scope.mceModel = editor.getContent();
});
@@ -200,8 +212,8 @@ module.exports = function (ngApp, events) {
scope.tinymce.extraSetups.push(tinyMceSetup);
// Custom tinyMCE plugins
tinymce.PluginManager.add('customhr', function(editor) {
editor.addCommand('InsertHorizontalRule', function() {
tinymce.PluginManager.add('customhr', function (editor) {
editor.addCommand('InsertHorizontalRule', function () {
var hrElem = document.createElement('hr');
var cNode = editor.selection.getNode();
var parentNode = cNode.parentNode;
@@ -227,7 +239,11 @@ module.exports = function (ngApp, events) {
}
}]);
ngApp.directive('markdownInput', ['$timeout', function($timeout) {
/**
* Markdown input
* Handles the logic for just the editor input field.
*/
ngApp.directive('markdownInput', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: {
@@ -237,11 +253,14 @@ module.exports = function (ngApp, events) {
link: function (scope, element, attrs) {
// Set initial model content
var content = element.val();
element = element.find('textarea').first();
let content = element.val();
scope.mdModel = content;
scope.mdChange(markdown(content));
element.on('change input', (e) => {
console.log('test');
element.on('change input', (event) => {
content = element.val();
$timeout(() => {
scope.mdModel = content;
@@ -251,7 +270,7 @@ module.exports = function (ngApp, events) {
scope.$on('markdown-update', (event, value) => {
element.val(value);
scope.mdModel= value;
scope.mdModel = value;
scope.mdChange(markdown(value));
});
@@ -259,41 +278,80 @@ module.exports = function (ngApp, events) {
}
}]);
ngApp.directive('markdownEditor', ['$timeout', function($timeout) {
/**
* Markdown Editor
* Handles all functionality of the markdown editor.
*/
ngApp.directive('markdownEditor', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
// Elements
var input = element.find('textarea[markdown-input]');
var insertImage = element.find('button[data-action="insertImage"]');
const input = element.find('[markdown-input] textarea').first();
const display = element.find('.markdown-display').first();
const insertImage = element.find('button[data-action="insertImage"]');
var currentCaretPos = 0;
let currentCaretPos = 0;
input.blur((event) => {
input.blur(event => {
currentCaretPos = input[0].selectionStart;
});
// Insert image shortcut
input.keydown((event) => {
// Scroll sync
let inputScrollHeight,
inputHeight,
displayScrollHeight,
displayHeight;
function setScrollHeights() {
inputScrollHeight = input[0].scrollHeight;
inputHeight = input.height();
displayScrollHeight = display[0].scrollHeight;
displayHeight = display.height();
}
setTimeout(() => {
setScrollHeights();
}, 200);
window.addEventListener('resize', setScrollHeights);
let scrollDebounceTime = 800;
let lastScroll = 0;
input.on('scroll', event => {
let now = Date.now();
if (now - lastScroll > scrollDebounceTime) {
setScrollHeights()
}
let scrollPercent = (input.scrollTop() / (inputScrollHeight - inputHeight));
let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent;
display.scrollTop(displayScrollY);
lastScroll = now;
});
// Editor key-presses
input.keydown(event => {
// Insert image shortcut
if (event.which === 73 && event.ctrlKey && event.shiftKey) {
event.preventDefault();
var caretPos = input[0].selectionStart;
var currentContent = input.val();
var mdImageText = "![](http://)";
let caretPos = input[0].selectionStart;
let currentContent = input.val();
const mdImageText = "![](http://)";
input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
input.focus();
input[0].selectionStart = caretPos + ("![](".length);
input[0].selectionEnd = caretPos + ('![](http://'.length);
return;
}
// Pass key presses to controller via event
scope.$emit('editor-keydown', event);
});
// Insert image from image manager
insertImage.click((event) => {
window.ImageManager.showExternal((image) => {
var caretPos = currentCaretPos;
var currentContent = input.val();
var mdImageText = "![" + image.name + "](" + image.url + ")";
insertImage.click(event => {
window.ImageManager.showExternal(image => {
let caretPos = currentCaretPos;
let currentContent = input.val();
let mdImageText = "![" + image.name + "](" + image.url + ")";
input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
input.change();
});
@@ -302,11 +360,16 @@ module.exports = function (ngApp, events) {
}
}
}]);
ngApp.directive('toolbox', [function() {
/**
* Page Editor Toolbox
* Controls all functionality for the sliding toolbox
* on the page edit view.
*/
ngApp.directive('toolbox', [function () {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
link: function (scope, elem, attrs) {
// Get common elements
const $buttons = elem.find('[tab-button]');
@@ -317,7 +380,7 @@ module.exports = function (ngApp, events) {
$toggle.click((e) => {
elem.toggleClass('open');
});
// Set an active tab/content by name
function setActive(tabName, openToolbox) {
$buttons.removeClass('active');
@@ -331,7 +394,7 @@ module.exports = function (ngApp, events) {
setActive($content.first().attr('tab-content'), false);
// Handle tab button click
$buttons.click(function(e) {
$buttons.click(function (e) {
let name = $(this).attr('tab-button');
setActive(name, true);
});
@@ -339,11 +402,16 @@ module.exports = function (ngApp, events) {
}
}]);
ngApp.directive('autosuggestions', ['$http', function($http) {
/**
* Tag Autosuggestions
* Listens to child inputs and provides autosuggestions depending on field type
* and input. Suggestions provided by server.
*/
ngApp.directive('tagAutosuggestions', ['$http', function ($http) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
link: function (scope, elem, attrs) {
// Local storage for quick caching.
const localCache = {};
@@ -360,38 +428,49 @@ module.exports = function (ngApp, events) {
let active = 0;
// Listen to input events on autosuggest fields
elem.on('input', '[autosuggest]', function(event) {
elem.on('input focus', '[autosuggest]', function (event) {
let $input = $(this);
let val = $input.val();
let url = $input.attr('autosuggest');
// No suggestions until at least 3 chars
if (val.length < 3) {
if (isShowing) {
$suggestionBox.hide();
isShowing = false;
let type = $input.attr('autosuggest-type');
// Add name param to request if for a value
if (type.toLowerCase() === 'value') {
let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first();
let nameVal = $nameInput.val();
if (nameVal !== '') {
url += '?name=' + encodeURIComponent(nameVal);
}
return;
};
}
let suggestionPromise = getSuggestions(val.slice(0, 3), url);
suggestionPromise.then((suggestions) => {
if (val.length > 2) {
suggestions = suggestions.filter((item) => {
return item.toLowerCase().indexOf(val.toLowerCase()) !== -1;
}).slice(0, 4);
displaySuggestions($input, suggestions);
}
suggestionPromise.then(suggestions => {
if (val.length === 0) {
displaySuggestions($input, suggestions.slice(0, 6));
} else {
suggestions = suggestions.filter(item => {
return item.toLowerCase().indexOf(val.toLowerCase()) !== -1;
}).slice(0, 4);
displaySuggestions($input, suggestions);
}
});
});
// Hide autosuggestions when input loses focus.
// Slight delay to allow clicks.
elem.on('blur', '[autosuggest]', function(event) {
let lastFocusTime = 0;
elem.on('blur', '[autosuggest]', function (event) {
let startTime = Date.now();
setTimeout(() => {
$suggestionBox.hide();
isShowing = false;
if (lastFocusTime < startTime) {
$suggestionBox.hide();
isShowing = false;
}
}, 200)
});
elem.on('focus', '[autosuggest]', function (event) {
lastFocusTime = Date.now();
});
elem.on('keydown', '[autosuggest]', function (event) {
if (!isShowing) return;
@@ -401,23 +480,25 @@ module.exports = function (ngApp, events) {
// Down arrow
if (event.keyCode === 40) {
let newActive = (active === suggestCount-1) ? 0 : active + 1;
let newActive = (active === suggestCount - 1) ? 0 : active + 1;
changeActiveTo(newActive, suggestionElems);
}
// Up arrow
else if (event.keyCode === 38) {
let newActive = (active === 0) ? suggestCount-1 : active - 1;
let newActive = (active === 0) ? suggestCount - 1 : active - 1;
changeActiveTo(newActive, suggestionElems);
}
// Enter key
else if (event.keyCode === 13) {
// Enter or tab key
else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
let text = suggestionElems[active].textContent;
currentInput[0].value = text;
currentInput.focus();
$suggestionBox.hide();
isShowing = false;
event.preventDefault();
return false;
if (event.keyCode === 13) {
event.preventDefault();
return false;
}
}
});
@@ -430,6 +511,7 @@ module.exports = function (ngApp, events) {
// Display suggestions on a field
let prevSuggestions = [];
function displaySuggestions($input, suggestions) {
// Hide if no suggestions
@@ -466,7 +548,8 @@ module.exports = function (ngApp, events) {
if (i === 0) {
suggestion.className = 'active'
active = 0;
};
}
;
$suggestionBox[0].appendChild(suggestion);
}
@@ -484,17 +567,18 @@ module.exports = function (ngApp, events) {
// Get suggestions & cache
function getSuggestions(input, url) {
let searchUrl = url + '?search=' + encodeURIComponent(input);
let hasQuery = url.indexOf('?') !== -1;
let searchUrl = url + (hasQuery ? '&' : '?') + 'search=' + encodeURIComponent(input);
// Get from local cache if exists
if (localCache[searchUrl]) {
if (typeof localCache[searchUrl] !== 'undefined') {
return new Promise((resolve, reject) => {
resolve(localCache[input]);
resolve(localCache[searchUrl]);
});
}
return $http.get(searchUrl).then((response) => {
localCache[input] = response.data;
return $http.get(searchUrl).then(response => {
localCache[searchUrl] = response.data;
return response.data;
});
}
@@ -502,6 +586,67 @@ module.exports = function (ngApp, events) {
}
}
}]);
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 window.baseUrl(`/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;
});
};
}
};
}]);
};

View File

@@ -7,6 +7,14 @@ var ngAnimate = require('angular-animate');
var ngSanitize = require('angular-sanitize');
require('angular-ui-sortable');
// Url retrieval function
window.baseUrl = function(path) {
let basePath = document.querySelector('meta[name="base-url"]').getAttribute('content');
if (basePath[basePath.length-1] === '/') basePath = basePath.slice(0, basePath.length-1);
if (path[0] === '/') path = path.slice(1);
return basePath + '/' + path;
};
var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
// Global Event System
@@ -29,6 +37,7 @@ var Events = {
};
window.Events = Events;
var services = require('./services')(ngApp, Events);
var directives = require('./directives')(ngApp, Events);
var controllers = require('./controllers')(ngApp, Events);
@@ -112,16 +121,11 @@ $(function () {
// Common jQuery actions
$('[data-action="expand-entity-list-details"]').click(function() {
$('.entity-list.compact').find('p').slideToggle(240);
$('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
});
});
function elemExists(selector) {
return document.querySelector(selector) !== null;
}
// Page specific items
require('./pages/page-show');

View File

@@ -1,7 +1,8 @@
var mceOptions = module.exports = {
selector: '#html-editor',
content_css: [
'/css/styles.css'
window.baseUrl('/css/styles.css'),
window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
],
body_class: 'page-content',
relative_urls: false,
@@ -19,11 +20,18 @@ var mceOptions = module.exports = {
{title: "Header 1", format: "h1"},
{title: "Header 2", format: "h2"},
{title: "Header 3", format: "h3"},
{title: "Paragraph", format: "p"},
{title: "Paragraph", format: "p", exact: true, classes: ''},
{title: "Blockquote", format: "blockquote"},
{title: "Code Block", icon: "code", format: "pre"},
{title: "Inline Code", icon: "code", inline: "code"}
{title: "Inline Code", icon: "code", inline: "code"},
{title: "Callouts", items: [
{title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
{title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
{title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
{title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
]}
],
style_formats_merge: false,
formats: {
alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
@@ -140,7 +148,7 @@ var mceOptions = module.exports = {
formData.append('file', file, remoteFilename);
formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
xhr.open('POST', '/images/gallery/upload');
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
xhr.onload = function () {
if (xhr.status === 200 || xhr.status === 201) {
var result = JSON.parse(xhr.responseText);

View File

@@ -2,7 +2,7 @@
// Configure ZeroClipboard
var zeroClipBoard = require('zeroclipboard');
zeroClipBoard.config({
swfPath: '/ZeroClipboard.swf'
swfPath: window.baseUrl('/ZeroClipboard.swf')
});
window.setupPageShow = module.exports = function (pageId) {
@@ -36,7 +36,8 @@ window.setupPageShow = module.exports = function (pageId) {
// Show pointer and set link
var $elem = $(this);
var link = window.location.protocol + "//" + window.location.host + '/link/' + pageId + '#' + $elem.attr('id');
let link = window.baseUrl('/link/' + pageId + '#' + $elem.attr('id'));
if (link.indexOf('http') !== 0) link = window.location.protocol + "//" + window.location.host + link;
$pointer.find('input').val(link);
$pointer.find('button').first().attr('data-clipboard-text', link);
$elem.before($pointer);
@@ -74,15 +75,15 @@ window.setupPageShow = module.exports = function (pageId) {
// Make the book-tree sidebar stick in view on scroll
var $window = $(window);
var $bookTree = $(".book-tree");
var $bookTreeParent = $bookTree.parent();
// Check the page is scrollable and the content is taller than the tree
var pageScrollable = ($(document).height() > $window.height()) && ($bookTree.height() < $('.page-content').height());
// Get current tree's width and header height
var headerHeight = $("#header").height() + $(".toolbar").height();
var isFixed = $window.scrollTop() > headerHeight;
var bookTreeWidth = $bookTree.width();
// Function to fix the tree as a sidebar
function stickTree() {
$bookTree.width(bookTreeWidth + 48 + 15);
$bookTree.width($bookTreeParent.width() + 15);
$bookTree.addClass("fixed");
isFixed = true;
}
@@ -101,13 +102,27 @@ window.setupPageShow = module.exports = function (pageId) {
unstickTree();
}
}
// The event ran when the window scrolls
function windowScrollEvent() {
checkTreeStickiness(false);
}
// If the page is scrollable and the window is wide enough listen to scroll events
// and evaluate tree stickiness.
if (pageScrollable && $window.width() > 1000) {
$window.scroll(function() {
checkTreeStickiness(false);
});
$window.on('scroll', windowScrollEvent);
checkTreeStickiness(true);
}
// Handle window resizing and switch between desktop/mobile views
$window.on('resize', event => {
if (pageScrollable && $window.width() > 1000) {
$window.on('scroll', windowScrollEvent);
checkTreeStickiness(true);
} else {
$window.off('scroll', windowScrollEvent);
unstickTree();
}
});
};

View File

@@ -125,3 +125,51 @@
margin-right: $-xl;
}
}
/**
* Callouts
*/
.callout {
border-left: 3px solid #BBB;
background-color: #EEE;
padding: $-s;
&:before {
font-family: 'Material-Design-Iconic-Font';
padding-right: $-s;
display: inline-block;
}
&.success {
border-left-color: $positive;
background-color: lighten($positive, 45%);
color: darken($positive, 16%);
}
&.success:before {
content: '\f269';
}
&.danger {
border-left-color: $negative;
background-color: lighten($negative, 34%);
color: darken($negative, 20%);
}
&.danger:before {
content: '\f1f2';
}
&.info {
border-left-color: $info;
background-color: lighten($info, 50%);
color: darken($info, 16%);
}
&.info:before {
content: '\f1f8';
}
&.warning {
border-left-color: $warning;
background-color: lighten($warning, 36%);
color: darken($warning, 16%);
}
&.warning:before {
content: '\f1f1';
}
}

View File

@@ -6,8 +6,8 @@
font-style: normal;
font-weight: 100;
src: local('Roboto Thin'), local('Roboto-Thin'),
url('/fonts/roboto-v15-cyrillic_latin-100.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-100.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-100.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-100.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-100italic - cyrillic_latin */
@font-face {
@@ -15,8 +15,8 @@
font-style: italic;
font-weight: 100;
src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'),
url('/fonts/roboto-v15-cyrillic_latin-100italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-100italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-100italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-100italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-300 - cyrillic_latin */
@font-face {
@@ -24,8 +24,8 @@
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'),
url('/fonts/roboto-v15-cyrillic_latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-300italic - cyrillic_latin */
@font-face {
@@ -33,8 +33,8 @@
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'),
url('/fonts/roboto-v15-cyrillic_latin-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-regular - cyrillic_latin */
@font-face {
@@ -42,8 +42,8 @@
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('/fonts/roboto-v15-cyrillic_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-italic - cyrillic_latin */
@font-face {
@@ -51,8 +51,8 @@
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'),
url('/fonts/roboto-v15-cyrillic_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-500 - cyrillic_latin */
@font-face {
@@ -60,8 +60,8 @@
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'),
url('/fonts/roboto-v15-cyrillic_latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-500italic - cyrillic_latin */
@font-face {
@@ -69,8 +69,8 @@
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url('/fonts/roboto-v15-cyrillic_latin-500italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-500italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-500italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-500italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-700 - cyrillic_latin */
@font-face {
@@ -78,8 +78,8 @@
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'),
url('/fonts/roboto-v15-cyrillic_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-700italic - cyrillic_latin */
@font-face {
@@ -87,8 +87,8 @@
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'),
url('/fonts/roboto-v15-cyrillic_latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-v15-cyrillic_latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-v15-cyrillic_latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-v15-cyrillic_latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* roboto-mono-regular - latin */
@@ -97,6 +97,6 @@
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'),
url('/fonts/roboto-mono-v4-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('/fonts/roboto-mono-v4-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
url('../fonts/roboto-mono-v4-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
url('../fonts/roboto-mono-v4-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

View File

@@ -20,6 +20,9 @@
&.disabled, &[disabled] {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==);
}
&:focus {
outline: 0;
}
}
#html-editor {

View File

@@ -23,7 +23,7 @@ button {
table {
min-width: 100px;
td {
td, th {
min-width: 10px;
padding: 4px 6px;
border: 1px solid #DDD;

View File

@@ -1,6 +1,7 @@
.page-list {
h3 {
margin: $-l 0 $-m 0;
margin: $-l 0 $-xs 0;
font-size: 1.666em;
}
a.chapter {
color: $color-chapter;
@@ -8,7 +9,6 @@
.inset-list {
display: none;
overflow: hidden;
// padding-left: $-m;
margin-bottom: $-l;
}
h4 {
@@ -338,6 +338,10 @@ ul.pagination {
padding-top: $-xs;
margin: 0;
}
> p.empty-text {
display: block;
font-size: $fs-m;
}
hr {
margin: 0;
}

View File

@@ -48,7 +48,7 @@
max-width: 100%;
height:auto;
}
h1, h2, h3, h4, h5, h6 {
h1, h2, h3, h4, h5, h6, pre {
clear: left;
}
hr {
@@ -158,8 +158,8 @@
.tabs {
display: block;
border-right: 1px solid #DDD;
width: 54px;
flex: 0;
width: 48px;
flex: 0 1 auto;
}
.tabs i {
color: rgba(0, 0, 0, 0.5);
@@ -250,13 +250,10 @@
margin: 0;
padding: 0;
}
span {
color: #666;
margin-left: $-s;
}
.heading {
.heading th {
padding: $-xs $-s;
color: #444;
color: #333;
font-weight: 400;
}
td {
border: 0;
@@ -267,9 +264,6 @@
.tag-value {
color: #888;
}
td i {
color: #888;
}
tr:last-child td {
border-bottom: none;
}

View File

@@ -3,7 +3,7 @@
*/
h1 {
font-size: 3.625em;
font-size: 3.425em;
line-height: 1.22222222em;
margin-top: 0.48888889em;
margin-bottom: 0.48888889em;
@@ -33,10 +33,10 @@ h1, h2, h3, h4 {
display: block;
color: #555;
.subheader {
display: block;
//display: block;
font-size: 0.5em;
line-height: 1em;
color: lighten($text-dark, 16%);
color: lighten($text-dark, 32%);
}
}
@@ -225,6 +225,15 @@ p.secondary, p .secondary, span.secondary, .text-secondary {
color: $color-chapter;
}
}
.faded .text-book:hover {
color: $color-book !important;
}
.faded .text-chapter:hover {
color: $color-chapter !important;
}
.faded .text-page:hover {
color: $color-page !important;
}
span.highlight {
//background-color: rgba($primary, 0.2);

View File

@@ -24,21 +24,22 @@
text-align: center;
}
.edit-area.flex > .mce-tinymce.mce-container.mce-panel {
height: 100%;
max-height: 100%;
flex: 1;
.edit-area.flex > div > .mce-tinymce.mce-container.mce-panel {
flex: 1 1 auto;
display: flex !important;
flex-direction: column;
align-items: stretch;
margin: 0 -1px;
> .mce-container-body {
flex: 1;
flex: 1 1 auto;
display: flex !important;
flex-direction: column;
align-items: stretch;
> .mce-toolbar-grp {
flex: 0 1 auto;
}
> .mce-edit-area {
flex: 1;
flex: 1 1 auto;
display: flex !important;
flex-direction: column;
align-items: stretch;

View File

@@ -38,6 +38,7 @@ $primary-dark: #0288D1;
$secondary: #e27b41;
$positive: #52A256;
$negative: #E84F4F;
$info: $primary;
$warning: $secondary;
$primary-faded: rgba(21, 101, 192, 0.15);

View File

@@ -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, span {
color: #EEE;
}
}

View File

@@ -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
@@ -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',
@@ -24,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',

View File

@@ -6,5 +6,5 @@
<div class="form-group">
<label for="password">Password</label>
@include('form/password', ['name' => 'password', 'tabindex' => 2])
<span class="block small"><a href="/password/email">Forgot Password?</a></span>
<span class="block small"><a href="{{ baseUrl('/password/email') }}">Forgot Password?</a></span>
</div>

View File

@@ -1,8 +1,8 @@
@extends('public')
@section('header-buttons')
@if(Setting::get('registration-enabled'))
<a href="/register"><i class="zmdi zmdi-account-add"></i>Sign up</a>
@if(setting('registration-enabled', false))
<a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a>
@endif
@stop
@@ -12,7 +12,7 @@
<div class="center-box">
<h1>Log In</h1>
<form action="/login" method="POST" id="login-form">
<form action="{{ baseUrl("/login") }}" method="POST" id="login-form">
{!! csrf_field() !!}
@@ -34,10 +34,10 @@
<hr class="margin-top">
<h3 class="text-muted">Social Login</h3>
@if(isset($socialDrivers['google']))
<a href="/login/service/google" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
<a href="{{ baseUrl("/login/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
@endif
@if(isset($socialDrivers['github']))
<a href="/login/service/github" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
<a href="{{ baseUrl("/login/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
@endif
@endif
</div>

View File

@@ -1,7 +1,5 @@
@extends('public')
@section('body-class', 'image-cover login')
@section('content')
@@ -11,7 +9,7 @@
<p class="muted small">Enter your email below and you will be sent an email with a password reset link.</p>
<form action="/password/email" method="POST">
<form action="{{ baseUrl("/password/email") }}" method="POST">
{!! csrf_field() !!}
<div class="form-group">

View File

@@ -2,7 +2,7 @@
@section('header-buttons')
@if(!$signedIn)
<a href="/login"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
<a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
@endif
@stop
@@ -11,7 +11,7 @@
<div class="text-center">
<div class="center-box">
<h2>Thanks for registering!</h2>
<p>Please check your email and click the confirmation button to access {{ \Setting::get('app-name') }}.</p>
<p>Please check your email and click the confirmation button to access {{ setting('app-name', 'BookStack') }}.</p>
</div>
</div>

View File

@@ -1,7 +1,7 @@
@extends('public')
@section('header-buttons')
<a href="/login"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
<a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
@stop
@section('content')
@@ -10,7 +10,7 @@
<div class="center-box">
<h1>Sign Up</h1>
<form action="/register" method="POST">
<form action="{{ baseUrl("/register") }}" method="POST">
{!! csrf_field() !!}
<div class="form-group">
@@ -38,10 +38,10 @@
<h3 class="text-muted">Social Registration</h3>
<p class="text-small">Register and sign in using another service.</p>
@if(isset($socialDrivers['google']))
<a href="/register/service/google" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
<a href="{{ baseUrl("/register/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
@endif
@if(isset($socialDrivers['github']))
<a href="/register/service/github" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
<a href="{{ baseUrl("/register/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
@endif
@endif
</div>

View File

@@ -9,7 +9,7 @@
<div class="center-box text-left">
<h1>Reset Password</h1>
<form action="/password/reset" method="POST">
<form action="{{ baseUrl("/password/reset") }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="token" value="{{ $token }}">

View File

@@ -10,7 +10,7 @@
If you cannot find the email you can re-send the confirmation email by submitting the form below.
</p>
<hr>
<form action="/register/confirm/resend" method="POST">
<form action="{{ baseUrl("/register/confirm/resend") }}" method="POST">
{!! csrf_field() !!}
<div class="form-group">
<label for="email">Email Address</label>

View File

@@ -1,21 +1,22 @@
<!DOCTYPE html>
<html class="@yield('body-class')">
<head>
<title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name', 'BookStack') }}</title>
<title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title>
<!-- Meta -->
<meta name="viewport" content="width=device-width">
<meta name="token" content="{{ csrf_token() }}">
<meta name="base-url" content="{{ baseUrl('/') }}">
<meta charset="utf-8">
<!-- Styles and Fonts -->
<link rel="stylesheet" href="{{ versioned_asset('css/styles.css') }}">
<link rel="stylesheet" media="print" href="{{ versioned_asset('css/print-styles.css') }}">
<link rel="stylesheet" href="/libs/material-design-iconic-font/css/material-design-iconic-font.min.css">
<link rel="stylesheet" href="{{ baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css') }}">
<!-- Scripts -->
<script src="/libs/jquery/jquery.min.js?version=2.1.4"></script>
<script src="/libs/jquery/jquery-ui.min.js?version=1.11.4"></script>
<script src="{{ baseUrl('/libs/jquery/jquery.min.js?version=2.1.4') }}"></script>
<script src="{{ baseUrl('/libs/jquery/jquery-ui.min.js?version=1.11.4') }}"></script>
@yield('head')
@@ -34,15 +35,15 @@
<div class="container">
<div class="row">
<div class="col-lg-4 col-sm-4" ng-non-bindable>
<a href="/" class="logo">
<a href="{{ baseUrl('/') }}" class="logo">
@if(setting('app-logo', '') !== 'none')
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? '/logo.png' : setting('app-logo', '') }}" alt="Logo">
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
@endif
<span class="logo-text">{{ setting('app-name', 'BookStack') }}</span>
<span class="logo-text">{{ setting('app-name') }}</span>
</a>
</div>
<div class="col-lg-4 col-sm-3 text-center">
<form action="/search/all" method="GET" class="search-box">
<form action="{{ baseUrl('/search/all') }}" method="GET" class="search-box">
<input id="header-search-box-input" type="text" name="term" tabindex="2" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
<button id="header-search-box-button" type="submit" class="text-button"><i class="zmdi zmdi-search"></i></button>
</form>
@@ -50,12 +51,12 @@
<div class="col-lg-4 col-sm-5">
<div class="float right">
<div class="links text-center">
<a href="/books"><i class="zmdi zmdi-book"></i>Books</a>
<a href="{{ baseUrl('/books') }}"><i class="zmdi zmdi-book"></i>Books</a>
@if(isset($currentUser) && userCan('settings-manage'))
<a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a>
<a href="{{ baseUrl('/settings') }}"><i class="zmdi zmdi-settings"></i>Settings</a>
@endif
@if(!isset($signedIn) || !$signedIn)
<a href="/login"><i class="zmdi zmdi-sign-in"></i>Sign In</a>
<a href="{{ baseUrl('/login') }}"><i class="zmdi zmdi-sign-in"></i>Sign In</a>
@endif
</div>
@if(isset($signedIn) && $signedIn)
@@ -66,13 +67,13 @@
</span>
<ul>
<li>
<a href="/user/{{$currentUser->id}}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>View Profile</a>
<a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>View Profile</a>
</li>
<li>
<a href="/settings/users/{{$currentUser->id}}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>Edit Profile</a>
<a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>Edit Profile</a>
</li>
<li>
<a href="/logout" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>Logout</a>
<a href="{{ baseUrl('/logout') }}" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>Logout</a>
</li>
</ul>
</div>

View File

@@ -4,7 +4,7 @@
<div class="container small" ng-non-bindable>
<h1>Create New Book</h1>
<form action="/books" method="POST">
<form action="{{ baseUrl("/books") }}" method="POST">
@include('books/form')
</form>
</div>

View File

@@ -4,7 +4,7 @@
<div class="container small" ng-non-bindable>
<h1>Edit Book</h1>
<form action="/books/{{$book->slug}}" method="POST">
<form action="{{ $book->getUrl() }}" method="POST">
<input type="hidden" name="_method" value="PUT">
@include('books/form', ['model' => $book])
</form>

View File

@@ -9,7 +9,7 @@
<div class="col-xs-11 faded">
<div class="action-buttons">
@if($currentUser->can('book-create-all'))
<a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
<a href="{{ baseUrl("/books/create") }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
@endif
</div>
</div>
@@ -31,7 +31,7 @@
@else
<p class="text-muted">No books have been created.</p>
@if(userCan('books-create-all'))
<a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a>
<a href="{{ baseUrl("/books/create") }}" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a>
@endif
@endif
</div>

View File

@@ -1,4 +1,4 @@
<div class="book">
<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>

View File

@@ -8,10 +8,10 @@
<div class="col-md-12">
<div class="action-buttons faded">
@if(userCan('page-create', $book))
<a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
<a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
@endif
@if(userCan('chapter-create', $book))
<a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
<a href="{{ $book->getUrl('/chapter/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
@endif
@if(userCan('book-update', $book))
<a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
@@ -21,13 +21,13 @@
<a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
<ul>
@if(userCan('book-update', $book))
<li><a href="{{ $book->getUrl() }}/sort" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li>
<li><a href="{{ $book->getUrl('/sort') }}" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li>
@endif
@if(userCan('restrictions-manage', $book))
<li><a href="{{$book->getUrl()}}/permissions" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li>
<li><a href="{{ $book->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li>
@endif
@if(userCan('book-delete', $book))
<li><a href="{{ $book->getUrl() }}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
<li><a href="{{ $book->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
@endif
</ul>
</div>
@@ -61,16 +61,16 @@
@else
<p class="text-muted">No pages or chapters have been created for this book.</p>
<p>
<a href="{{$book->getUrl() . '/page/create'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
<a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
&nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
<a href="{{$book->getUrl() . '/chapter/create'}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
<a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a>
</p>
<hr>
@endif
<p class="text-muted small">
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by <a href="/user/{{ $book->createdBy->id }}">{{$book->createdBy->name}}</a> @endif
Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by <a href="{{ $book->createdBy->getProfileUrl() }}">{{$book->createdBy->name}}</a> @endif
<br>
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->updatedBy) by <a href="/user/{{ $book->updatedBy->id }}">{{$book->updatedBy->name}}</a> @endif
Last Updated {{$book->updated_at->diffForHumans()}} @if($book->updatedBy) by <a href="{{ $book->updatedBy->getProfileUrl() }}">{{$book->updatedBy->name}}</a> @endif
</p>
</div>
</div>
@@ -90,7 +90,7 @@
@if($book->restricted)
<p class="text-muted">
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
<a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Book Permissions Active
@endif

View File

@@ -1,7 +1,7 @@
@extends('base')
@section('head')
<script src="/libs/jquery-sortable/jquery-sortable.min.js"></script>
<script src="{{ baseUrl("/libs/jquery-sortable/jquery-sortable.min.js") }}"></script>
@stop
@section('content')
@@ -22,7 +22,7 @@
@foreach($books as $otherBook)
@if($otherBook->id !== $book->id)
<div>
<a href="/books/{{ $otherBook->slug }}/sort-item" class="text-book"><i class="zmdi zmdi-book"></i>{{ $otherBook->name }}</a>
<a href="{{ $otherBook->getUrl('/sort-item') }}" class="text-book"><i class="zmdi zmdi-book"></i>{{ $otherBook->name }}</a>
</div>
@endif
@endforeach
@@ -32,12 +32,12 @@
</div>
<form action="{{$book->getUrl()}}/sort" method="POST">
<form action="{{ $book->getUrl('/sort') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
<input type="hidden" id="sort-tree-input" name="sort-tree">
<div class="list">
<a href="{{$book->getUrl()}}" class="button muted">Cancel</a>
<a href="{{ $book->getUrl() }}" class="button muted">Cancel</a>
<button class="button pos" type="submit">Save Order</button>
</div>
</form>

View File

@@ -4,7 +4,7 @@
<div class="container small" ng-non-bindable>
<h1>Create New Chapter</h1>
<form action="{{$book->getUrl()}}/chapter/create" method="POST">
<form action="{{ $book->getUrl('/chapter/create') }}" method="POST">
@include('chapters/form')
</form>
</div>

View File

@@ -8,10 +8,10 @@
and added directly to the book.</p>
<p class="text-neg">Are you sure you want to delete this chapter?</p>
<form action="{{$chapter->getUrl()}}" method="POST">
<form action="{{ $chapter->getUrl() }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="DELETE">
<a href="{{$chapter->getUrl()}}" class="button primary">Cancel</a>
<a href="{{ $chapter->getUrl() }}" class="button primary">Cancel</a>
<button type="submit" class="button neg">Confirm</button>
</form>
</div>

View File

@@ -4,7 +4,7 @@
<div class="container small" ng-non-bindable>
<h1>Edit Chapter</h1>
<form action="{{$chapter->getUrl()}}" method="POST">
<form action="{{ $chapter->getUrl() }}" method="POST">
<input type="hidden" name="_method" value="PUT">
@include('chapters/form', ['model' => $chapter])
</form>

View File

@@ -1,5 +1,11 @@
<div class="chapter">
<div class="chapter entity-list-item" data-entity-type="chapter" data-entity-id="{{$chapter->id}}">
<h3>
@if (isset($showPath) && $showPath)
<a href="{{ $chapter->book->getUrl() }}" class="text-book">
<i class="zmdi zmdi-book"></i>{{ $chapter->book->name }}
</a>
<span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
@endif
<a href="{{ $chapter->getUrl() }}" class="text-chapter">
<i class="zmdi zmdi-collection-bookmark"></i>{{ $chapter->name }}
</a>
@@ -14,7 +20,7 @@
<p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p>
<div class="inset-list">
@foreach($chapter->pages as $page)
<h4 class="@if($page->draft) draft @endif"><a href="{{$page->getUrl()}}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
<h4 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h4>
@endforeach
</div>
@endif

View 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">&raquo;</span>
<a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></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

View File

@@ -7,7 +7,7 @@
<div class="row">
<div class="col-sm-12 faded">
<div class="breadcrumbs">
<a href="{{$chapter->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
<a href="{{ $chapter->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
<span class="sep">&raquo;</span>
<a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a>
</div>

View File

@@ -2,27 +2,37 @@
@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>
<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>
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
@endif
@if(userCan('chapter-update', $chapter))
<a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
<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>
@@ -50,22 +60,22 @@
<p class="text-muted">No pages are currently in this chapter.</p>
<p>
@if(userCan('page-create', $chapter))
<a href="{{$chapter->getUrl() . '/create-page'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
@endif
@if(userCan('page-create', $chapter) && userCan('book-update', $book))
&nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
@endif
@if(userCan('book-update', $book))
<a href="{{$book->getUrl() . '/sort'}}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a>
<a href="{{ $book->getUrl('/sort') }}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a>
@endif
</p>
<hr>
@endif
<p class="text-muted small">
Created {{$chapter->created_at->diffForHumans()}} @if($chapter->createdBy) by <a href="/user/{{ $chapter->createdBy->id }}">{{ $chapter->createdBy->name}}</a> @endif
Created {{ $chapter->created_at->diffForHumans() }} @if($chapter->createdBy) by <a href="{{ $chapter->createdBy->getProfileUrl() }}">{{ $chapter->createdBy->name}}</a> @endif
<br>
Last Updated {{$chapter->updated_at->diffForHumans()}} @if($chapter->updatedBy) by <a href="/user/{{ $chapter->updatedBy->id }}">{{ $chapter->updatedBy->name}}</a> @endif
Last Updated {{ $chapter->updated_at->diffForHumans() }} @if($chapter->updatedBy) by <a href="{{ $chapter->updatedBy->getProfileUrl() }}">{{ $chapter->updatedBy->name}}</a> @endif
</p>
</div>
<div class="col-md-3 col-md-offset-1">
@@ -75,7 +85,7 @@
@if($book->restricted)
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
<a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Book Permissions Active
@endif
@@ -84,7 +94,7 @@
@if($chapter->restricted)
@if(userCan('restrictions-manage', $chapter))
<a href="{{ $chapter->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a>
<a href="{{ $chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active
@endif

View File

@@ -1,176 +1,190 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<head style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<meta name="viewport" content="width=device-width" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;" />
<title>Confirm Your Email At {{ Setting::get('app-name')}}</title>
<meta name="viewport" content="width=device-width"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"/>
<title>Confirm Your Email At {{ setting('app-name')}}</title>
<style style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
* {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
font-size: 100%;
line-height: 1.6;
margin: 0;
padding: 0;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
font-size: 100%;
line-height: 1.6;
}
img {
max-width: 100%;
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100%!important;
height: 100%;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
}
a {
color: #348eda;
color: #348eda;
}
.btn-primary {
text-decoration: none;
color: #FFF;
background-color: #348eda;
border: solid #348eda;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 4px;
text-decoration: none;
color: #FFF;
background-color: #348eda;
border: solid #348eda;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 4px;
}
.btn-secondary {
text-decoration: none;
color: #FFF;
background-color: #aaa;
border: solid #aaa;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 25px;
text-decoration: none;
color: #FFF;
background-color: #aaa;
border: solid #aaa;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 25px;
}
.last {
margin-bottom: 0;
margin-bottom: 0;
}
.first {
margin-top: 0;
margin-top: 0;
}
.padding {
padding: 10px 0;
padding: 10px 0;
}
table.body-wrap {
width: 100%;
padding: 20px;
width: 100%;
padding: 20px;
}
table.body-wrap .container {
border: 1px solid #f0f0f0;
border: 1px solid #f0f0f0;
}
h1,
h2,
h3 {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
color: #444;
margin: 10px 0 10px;
line-height: 1.2;
font-weight: 200;
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
color: #444;
margin: 10px 0 10px;
line-height: 1.2;
font-weight: 200;
}
h1 {
font-size: 36px;
font-size: 36px;
}
h2 {
font-size: 28px;
font-size: 28px;
}
h3 {
font-size: 22px;
font-size: 22px;
}
p,
ul,
ol {
margin-bottom: 10px;
font-weight: normal;
font-size: 14px;
color: #888888;
margin-bottom: 10px;
font-weight: normal;
font-size: 14px;
color: #888888;
}
ul li,
ol li {
margin-left: 5px;
list-style-position: inside;
margin-left: 5px;
list-style-position: inside;
}
.container {
display: block!important;
max-width: 600px!important;
margin: 0 auto!important;
clear: both!important;
display: block !important;
max-width: 600px !important;
margin: 0 auto !important;
clear: both !important;
}
.body-wrap .container {
padding: 20px;
padding: 20px;
}
.content {
max-width: 600px;
margin: 0 auto;
display: block;
max-width: 600px;
margin: 0 auto;
display: block;
}
.content table {
width: 100%;
width: 100%;
}
</style>
</head>
</head>
<body bgcolor="#f6f6f6" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;width:100%!important;height:100%;">
<!-- body -->
<table class="body-wrap" bgcolor="#f6f6f6" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;">
<body bgcolor="#f6f6f6"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;width:100%!important;height:100%;">
<!-- body -->
<table class="body-wrap" bgcolor="#f6f6f6"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
<td class="container" bgcolor="#FFFFFF" style="font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;display:block!important;max-width:600px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;border-width:1px;border-style:solid;border-color:#f0f0f0;">
<!-- content -->
<div class="content" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;">
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;">Email Confirmation</h1>
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">Thanks for joining <a href="{{ url('/') }}">{{ Setting::get('app-name')}}</a>. <br />
Please confirm your email address by clicking the button below.</p>
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td class="padding" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;"><a class="btn-primary" href="{{ url('/register/confirm/'.$token) }}" style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm Email</a></p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<!-- /content -->
</td>
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
</tr>
</table>
<!-- /body -->
</body>
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
<td class="container" bgcolor="#FFFFFF"
style="font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;display:block!important;max-width:600px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;padding-top:20px;padding-bottom:20px;padding-right:20px;padding-left:20px;border-width:1px;border-style:solid;border-color:#f0f0f0;">
<!-- content -->
<div class="content"
style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;">
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<h1 style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;color:#444;margin-top:10px;margin-bottom:10px;margin-right:0;margin-left:0;line-height:1.2;font-weight:200;font-size:36px;">
Email Confirmation</h1>
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
Thanks for joining <a href="{{ baseUrl('/') }}">{{ setting('app-name')}}</a>. <br/>
Please confirm your email address by clicking the button below.</p>
<table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;width:100%;">
<tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;">
<td class="padding"
style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;padding-top:10px;padding-bottom:10px;padding-right:0;padding-left:0;">
<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;line-height:1.6;margin-bottom:10px;font-weight:normal;font-size:14px;color:#888888;">
<a class="btn-primary" href="{{ baseUrl('/register/confirm/' . $token) }}"
style="margin-top:0;margin-bottom:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;text-decoration:none;color:#FFF;background-color:#348eda;border-style:solid;border-color:#348eda;border-width:10px 20px;line-height:2;font-weight:bold;margin-right:10px;text-align:center;cursor:pointer;display:inline-block;border-radius:4px;">Confirm
Email</a></p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<!-- /content -->
</td>
<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;font-size:100%;line-height:1.6;"></td>
</tr>
</table>
<!-- /body -->
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
<div class="container">
<h1 class="text-muted">{{ $message or 'Page Not Found' }}</h1>
<p>Sorry, The page you were looking for could not be found.</p>
<a href="/" class="button">Return To Home</a>
<a href="{{ baseUrl('/') }}" class="button">Return To Home</a>
</div>
@stop

View File

@@ -1,47 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Be right back.</title>
@extends('public')
<link href="//fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
@section('content')
<style>
html, body {
height: 100%;
}
<div class="container">
<h1 class="text-muted">{{ setting('app-name') }} is down right now</h1>
<p>It will be back up soon.</p>
</div>
body {
margin: 0;
padding: 0;
width: 100%;
color: #B0BEC5;
display: table;
font-weight: 100;
font-family: 'Lato';
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 72px;
margin-bottom: 40px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">Be right back.</div>
</div>
</div>
</body>
</html>
@stop

View File

@@ -1,4 +1,4 @@
<form action="{{ $model->getUrl() }}/permissions" method="POST">
<form action="{{ $model->getUrl('/permissions') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">

View File

@@ -34,18 +34,30 @@
@else
<h3>Recent Books</h3>
@endif
@include('partials/entity-list', ['entities' => $recents, 'style' => 'compact'])
@include('partials/entity-list', [
'entities' => $recents,
'style' => 'compact',
'emptyText' => $signedIn ? 'You have not viewed any pages' : 'No books have been created'
])
</div>
<div class="col-sm-4">
<h3><a class="no-color" href="/pages/recently-created">Recently Created Pages</a></h3>
<h3><a class="no-color" href="{{ baseUrl("/pages/recently-created") }}">Recently Created Pages</a></h3>
<div id="recently-created-pages">
@include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
@include('partials/entity-list', [
'entities' => $recentlyCreatedPages,
'style' => 'compact',
'emptyText' => 'No pages have been recently created'
])
</div>
<h3><a class="no-color" href="/pages/recently-updated">Recently Updated Pages</a></h3>
<h3><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">Recently Updated Pages</a></h3>
<div id="recently-updated-pages">
@include('partials/entity-list', ['entities' => $recentlyUpdatedPages, 'style' => 'compact'])
@include('partials/entity-list', [
'entities' => $recentlyUpdatedPages,
'style' => 'compact',
'emptyText' => 'No pages have been recently updated'
])
</div>
</div>

View File

@@ -6,10 +6,10 @@
<h1>Delete {{ $page->draft ? 'Draft' : '' }} Page</h1>
<p class="text-neg">Are you sure you want to delete this {{ $page->draft ? 'draft' : '' }} page?</p>
<form action="{{$page->getUrl()}}" method="POST">
<form action="{{ $page->getUrl() }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="DELETE">
<a href="{{$page->getUrl()}}" class="button primary">Cancel</a>
<a href="{{ $page->getUrl() }}" class="button primary">Cancel</a>
<button type="submit" class="button neg">Confirm</button>
</form>
</div>

View File

@@ -1,7 +1,7 @@
@extends('base')
@section('head')
<script src="/libs/tinymce/tinymce.min.js?ver=4.3.7"></script>
<script src="{{ baseUrl('/libs/tinymce/tinymce.min.js?ver=4.3.7') }}"></script>
@stop
@section('body-class', 'flexbox')
@@ -9,7 +9,7 @@
@section('content')
<div class="flex-fill flex">
<form action="{{$page->getUrl()}}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
<form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
@if(!isset($isDraft))
<input type="hidden" name="_method" value="PUT">
@endif

View File

@@ -10,12 +10,12 @@
<h4>Page Tags</h4>
<div class="padded tags">
<p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p>
<table class="no-style" autosuggestions style="width: 100%;">
<table class="no-style" tag-autosuggestions style="width: 100%;">
<tbody ui-sortable="sortOptions" ng-model="tags" >
<tr ng-repeat="tag in tags track by $index">
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
<td><input autosuggest="/ajax/tags/suggest/names" class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
<td><input autosuggest="/ajax/tags/suggest/values" class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
<td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/names') }}" autosuggest-type="name" class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
<td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/values') }}" autosuggest-type="value" class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
<td width="10" ng-show="tags.length != 1" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
</tr>
</tbody>

View File

@@ -20,7 +20,7 @@
<a ng-click="forceDraftSave()" class="text-pos"><i class="zmdi zmdi-save"></i>Save Draft</a>
</li>
<li ng-if="isNewPageDraft">
<a href="{{$model->getUrl()}}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete Draft</a>
<a href="{{ $model->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete Draft</a>
</li>
</ul>
</div>
@@ -44,8 +44,11 @@
<div class="edit-area flex-fill flex">
@if(setting('app-editor') === 'wysiwyg')
<textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" 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>
<div tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" class="flex-fill flex">
<textarea id="html-editor" name="html" rows="5" ng-non-bindable
@if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
</div>
@if($errors->has('html'))
<div class="text-neg text-small">{{ $errors->first('html') }}</div>
@endif
@@ -61,8 +64,12 @@
<button class="text-button" type="button" data-action="insertImage"><i class="zmdi zmdi-image"></i>Insert Image</button>
</div>
</div>
<textarea markdown-input md-change="editorChange" md-model="editContent" name="markdown" rows="5"
@if($errors->has('markdown')) class="neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
<div markdown-input md-change="editorChange" md-model="editContent" class="flex flex-fill">
<textarea ng-non-bindable id="markdown-editor-input" name="markdown" rows="5"
@if($errors->has('markdown')) class="neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
</div>
</div>
<div class="markdown-editor-wrap">

View File

@@ -1,4 +1,4 @@
<div class="page {{$page->draft ? 'draft' : ''}}">
<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>
@@ -11,11 +11,11 @@
@if(isset($style) && $style === 'detailed')
<div class="row meta text-muted text-small">
<div class="col-md-4">
<div class="col-md-6">
Created {{$page->created_at->diffForHumans()}} @if($page->createdBy)by {{$page->createdBy->name}}@endif <br>
Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif
</div>
<div class="col-md-8">
<div class="col-md-6">
<a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName(30) }}</a>
<br>
@if($page->chapter)

View File

@@ -0,0 +1,40 @@
@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>
@if($page->hasChapter())
<span class="sep">&raquo;</span>
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
<i class="zmdi zmdi-collection-bookmark"></i>
{{ $page->chapter->getShortName() }}
</a>
@endif
<span class="sep">&raquo;</span>
<a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file-text"></i>{{ $page->getShortName() }}</a>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<h1>Move Page <small class="subheader">{{$page->name}}</small></h1>
<form action="{{ $page->getUrl('/move') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
@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>
</form>
</div>
@stop

View File

@@ -4,14 +4,20 @@
@if(count($page->tags) > 0)
<div class="tag-display float right">
<div class="heading primary-background-light">Page Tags</div>
<table>
@foreach($page->tags as $tag)
<tr class="tag">
<td @if(!$tag->value) colspan="2" @endif><a href="/search/all?term=%5B{{ urlencode($tag->name) }}%5D">{{ $tag->name }}</a></td>
@if($tag->value) <td class="tag-value"><a href="/search/all?term=%5B{{ urlencode($tag->name) }}%3D{{ urlencode($tag->value) }}%5D">{{$tag->value}}</a></td> @endif
<thead>
<tr class="text-left heading primary-background-light">
<th colspan="2">Page Tags</th>
</tr>
@endforeach
</thead>
<tbody>
@foreach($page->tags as $tag)
<tr class="tag">
<td @if(!$tag->value) colspan="2" @endif><a href="{{ baseUrl('/search/all?term=%5B' . urlencode($tag->name) .'%5D') }}">{{ $tag->name }}</a></td>
@if($tag->value) <td class="tag-value"><a href="{{ baseUrl('/search/all?term=%5B' . urlencode($tag->name) .'%3D' . urlencode($tag->value) . '%5D') }}">{{$tag->value}}</a></td> @endif
</tr>
@endforeach
</tbody>
</table>
</div>
@endif

View File

@@ -3,12 +3,12 @@
@section('head')
<style>
body {
font-size: 15px;
line-height: 1;
font-size: 14px;
line-height: 1.2;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1;
line-height: 1.2;
}
table {
@@ -21,10 +21,21 @@
width: auto !important;
}
.page-content .float {
float: none !important;
}
.page-content img.align-left, .page-content img.align-right {
float: none !important;
clear: both;
display: block;
}
.tag-display {
min-width: 0;
max-width: none;
display: none;
}
</style>
@stop

View File

@@ -7,16 +7,16 @@
<div class="row">
<div class="col-sm-12 faded">
<div class="breadcrumbs">
<a href="{{$page->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
<a href="{{ $page->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
@if($page->hasChapter())
<span class="sep">&raquo;</span>
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
<i class="zmdi zmdi-collection-bookmark"></i>
{{$page->chapter->getShortName()}}
{{ $page->chapter->getShortName() }}
</a>
@endif
<span class="sep">&raquo;</span>
<a href="{{$page->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
<a href="{{ $page->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
</div>
</div>
</div>

View File

@@ -7,7 +7,7 @@
<div class="row">
<div class="col-md-6 faded">
<div class="breadcrumbs">
<a href="{{$page->getUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-arrow-left"></i>Back to page</a>
<a href="{{ $page->getUrl() }}" class="text-primary text-button"><i class="zmdi zmdi-arrow-left"></i>Back to page</a>
</div>
</div>
<div class="col-md-6 faded">
@@ -40,9 +40,9 @@
<td> @if($revision->createdBy) {{$revision->createdBy->name}} @else Deleted User @endif</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>
<a href="{{ $revision->getUrl() }}" target="_blank">Preview</a>
<span class="text-muted">&nbsp;|&nbsp;</span>
<a href="{{$revision->getUrl()}}/restore">Restore</a>
<a href="{{ $revision->getUrl('/restore') }}">Restore</a>
</td>
</tr>
@endforeach

View File

@@ -7,12 +7,12 @@
<div class="row">
<div class="col-sm-6 faded">
<div class="breadcrumbs">
<a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
<a href="{{ $book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
@if($page->hasChapter())
<span class="sep">&raquo;</span>
<a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
<i class="zmdi zmdi-collection-bookmark"></i>
{{$page->chapter->getShortName()}}
{{ $page->chapter->getShortName() }}
</a>
@endif
</div>
@@ -22,21 +22,32 @@
<span dropdown class="dropdown-container">
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div>
<ul class="wide">
<li><a href="{{$page->getUrl()}}/export/html" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li>
<li><a href="{{$page->getUrl()}}/export/pdf" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li>
<li><a href="{{$page->getUrl()}}/export/plaintext" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
<li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li>
<li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li>
<li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
</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('/edit') }}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a>
@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>
@endif
@if(userCan('page-delete', $page))
<a href="{{$page->getUrl()}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
@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))
<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))
<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>
@@ -62,13 +73,14 @@
<hr>
<p class="text-muted small">
Created {{$page->created_at->diffForHumans()}} @if($page->createdBy) by <a href="/user/{{ $page->createdBy->id }}">{{$page->createdBy->name}}</a> @endif
Created {{ $page->created_at->diffForHumans() }} @if($page->createdBy) by <a href="{{ $page->createdBy->getProfileUrl() }}">{{$page->createdBy->name}}</a> @endif
<br>
Last Updated {{$page->updated_at->diffForHumans()}} @if($page->updatedBy) by <a href="/user/{{ $page->updatedBy->id }}">{{$page->updatedBy->name}}</a> @endif
Last Updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy) by <a href="{{ $page->updatedBy->getProfileUrl() }}">{{$page->updatedBy->name}}</a> @endif
</p>
</div>
</div>
<div class="col-md-3 print-hidden">
<div class="margin-top large"></div>
@if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted)
@@ -76,7 +88,7 @@
@if($book->restricted)
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
<a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Book Permissions Active
@endif
@@ -85,7 +97,7 @@
@if($page->chapter && $page->chapter->restricted)
@if(userCan('restrictions-manage', $page->chapter))
<a href="{{ $page->chapter->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a>
<a href="{{ $page->chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active
@endif
@@ -94,7 +106,7 @@
@if($page->restricted)
@if(userCan('restrictions-manage', $page))
<a href="{{ $page->getUrl() }}/permissions"><i class="zmdi zmdi-lock-outline"></i>Page Permissions Active</a>
<a href="{{ $page->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Page Permissions Active</a>
@else
<i class="zmdi zmdi-lock-outline"></i>Page Permissions Active
@endif
@@ -103,8 +115,8 @@
</div>
@endif
@include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
</div>
</div>
</div>

View File

@@ -2,12 +2,12 @@
<div class="book-tree" ng-non-bindable>
<h6 class="text-muted">Book Navigation</h6>
<ul class="sidebar-page-list menu">
<li class="book-header"><a href="{{$book->getUrl()}}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li>
<li class="book-header"><a href="{{ $book->getUrl() }}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li>
@foreach($sidebarTree as $bookChild)
<li class="list-item-{{ $bookChild->getClassName() }} {{ $bookChild->getClassName() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
<a href="{{$bookChild->getUrl()}}" class="{{ $bookChild->getClassName() }} {{ $current->matches($bookChild)? 'selected' : '' }}">
<a href="{{ $bookChild->getUrl() }}" class="{{ $bookChild->getClassName() }} {{ $current->matches($bookChild)? 'selected' : '' }}">
@if($bookChild->isA('chapter'))<i class="zmdi zmdi-collection-bookmark"></i>@else <i class="zmdi zmdi-file-text"></i>@endif{{ $bookChild->name }}
</a>
@@ -18,7 +18,7 @@
<ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
@foreach($bookChild->pages as $childPage)
<li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}">
<a href="{{$childPage->getUrl()}}" class="page {{ $current->matches($childPage)? 'selected' : '' }}">
<a href="{{ $childPage->getUrl() }}" class="page {{ $current->matches($childPage)? 'selected' : '' }}">
<i class="zmdi zmdi-file-text"></i> {{ $childPage->name }}
</a>
</li>

View File

@@ -3,13 +3,13 @@
@if($activity->user)
<div class="left">
<img class="avatar" src="{{ $activity->user->getAvatar(30) }}" alt="{{$activity->user->name}}">
<img class="avatar" src="{{ $activity->user->getAvatar(30) }}" alt="{{ $activity->user->name }}">
</div>
@endif
<div class="right" ng-non-bindable>
@if($activity->user)
<a href="/user/{{ $activity->user->id }}">{{$activity->user->name}}</a>
<a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
@else
A deleted user
@endif
@@ -20,7 +20,7 @@
<a href="{{ $activity->entity->getUrl() }}">{{ $activity->entity->name }}</a>
@endif
@if($activity->extra) "{{$activity->extra}}" @endif
@if($activity->extra) "{{ $activity->extra }}" @endif
<br>

Some files were not shown because too many files have changed in this diff Show More