diff --git a/app/Exceptions/AuthException.php b/app/Exceptions/AuthException.php new file mode 100644 index 000000000..c20bb62a0 --- /dev/null +++ b/app/Exceptions/AuthException.php @@ -0,0 +1,4 @@ +exists) { + + // Check for users with same email already + $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0; + if ($alreadyUser) { + throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.'); + } + $user->save(); $this->userRepo->attachDefaultRole($user); auth()->login($user); @@ -184,14 +194,11 @@ class AuthController extends Controller } if (setting('registration-confirmation') || setting('registration-restrict')) { - $newUser->email_confirmed = false; $newUser->save(); $this->emailConfirmationService->sendConfirmation($newUser); return redirect('/register/confirm'); } - $newUser->email_confirmed = true; - auth()->login($newUser); session()->flash('success', 'Thanks for signing up! You are now registered and signed in.'); return redirect($this->redirectPath()); diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 5b2b510c9..7f743db41 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -1,13 +1,9 @@ -bookRepo->getBySlug($bookSlug); $this->checkOwnablePermission('restrictions-manage', $book); $this->bookRepo->updateRestrictionsFromRequest($request, $book); - session()->flash('success', 'Page Restrictions Updated'); + session()->flash('success', 'Book Restrictions Updated'); return redirect($book->getUrl()); } } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 6b8a2f18f..4641ddbdb 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -187,7 +187,7 @@ class ChapterController extends Controller $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter); - session()->flash('success', 'Page Restrictions Updated'); + session()->flash('success', 'Chapter Restrictions Updated'); return redirect($chapter->getUrl()); } } diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index f9d65c48b..2e5d5f303 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -1,14 +1,9 @@ -json($imgData); } + /** + * Search through images within a particular type. + * @param $type + * @param int $page + * @param Request $request + * @return mixed + */ + public function searchByType($type, $page = 0, Request $request) + { + $this->validate($request, [ + 'term' => 'required|string' + ]); + + $searchTerm = $request->get('term'); + $imgData = $this->imageRepo->searchPaginatedByType($type, $page,24, $searchTerm); + return response()->json($imgData); + } + /** * Get all images for a user. * @param int $page @@ -55,6 +68,27 @@ class ImageController extends Controller return response()->json($imgData); } + /** + * Get gallery images with a specific filter such as book or page + * @param $filter + * @param int $page + * @param Request $request + */ + public function getGalleryFiltered($filter, $page = 0, Request $request) + { + $this->validate($request, [ + 'page_id' => 'required|integer' + ]); + + $validFilters = collect(['page', 'book']); + if (!$validFilters->contains($filter)) return response('Invalid filter', 500); + + $pageId = $request->get('page_id'); + $imgData = $this->imageRepo->getGalleryFiltered($page, 24, strtolower($filter), $pageId); + + return response()->json($imgData); + } + /** * Handles image uploads for use on pages. * @param string $type diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index e2b10d3d3..a645ede02 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -4,6 +4,7 @@ use Activity; use BookStack\Exceptions\NotFoundException; use BookStack\Repos\UserRepo; use BookStack\Services\ExportService; +use Carbon\Carbon; use Illuminate\Http\Request; use BookStack\Http\Requests; use BookStack\Repos\BookRepo; @@ -88,7 +89,6 @@ class PageController extends Controller $input = $request->all(); $book = $this->bookRepo->getBySlug($bookSlug); - $input['priority'] = $this->bookRepo->getNewPriority($book); $draftPage = $this->pageRepo->getById($pageId, true); @@ -96,6 +96,12 @@ class PageController extends Controller $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book; $this->checkOwnablePermission('page-create', $parent); + if ($parent->isA('chapter')) { + $input['priority'] = $this->chapterRepo->getNewPriority($parent); + } else { + $input['priority'] = $this->bookRepo->getNewPriority($parent); + } + $page = $this->pageRepo->publishDraft($draftPage, $input); Activity::add($page, 'page_create', $book->id); @@ -164,6 +170,7 @@ class PageController extends Controller $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id); $page->name = $draft->name; $page->html = $draft->html; + $page->markdown = $draft->markdown; $page->isDraft = true; $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft); } @@ -204,12 +211,18 @@ class PageController extends Controller $page = $this->pageRepo->getById($pageId, true); $this->checkOwnablePermission('page-update', $page); if ($page->draft) { - $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html'])); + $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown'])); } else { - $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html'])); + $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html', 'markdown'])); } - $updateTime = $draft->updated_at->format('H:i'); - return response()->json(['status' => 'success', 'message' => 'Draft saved at ' . $updateTime]); + + $updateTime = $draft->updated_at->timestamp; + $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset; + return response()->json([ + 'status' => 'success', + 'message' => 'Draft saved at ', + 'timestamp' => $utcUpdateTimestamp + ]); } /** diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 81392fe6e..599f40c84 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -11,14 +11,12 @@ class Authenticate { /** * The Guard implementation. - * * @var Guard */ protected $auth; /** * Create a new filter instance. - * * @param Guard $auth */ public function __construct(Guard $auth) @@ -28,14 +26,13 @@ class Authenticate /** * Handle an incoming request. - * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { - if(auth()->check() && auth()->user()->email_confirmed == false) { + if ($this->auth->check() && setting('registration-confirmation') && !$this->auth->user()->email_confirmed) { return redirect()->guest('/register/confirm/awaiting'); } diff --git a/app/Http/routes.php b/app/Http/routes.php index eca37347c..9565b7576 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -75,6 +75,8 @@ Route::group(['middleware' => 'auth'], function () { Route::post('/{type}/upload', 'ImageController@uploadByType'); Route::get('/{type}/all', 'ImageController@getAllByType'); Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); + Route::get('/{type}/search/{page}', 'ImageController@searchByType'); + Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered'); Route::delete('/{imageId}', 'ImageController@destroy'); }); diff --git a/app/Page.php b/app/Page.php index 84e37d519..d2a303f61 100644 --- a/app/Page.php +++ b/app/Page.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class Page extends Entity { - protected $fillable = ['name', 'html', 'priority']; + protected $fillable = ['name', 'html', 'priority', 'markdown']; protected $simpleAttributes = ['name', 'id', 'slug']; diff --git a/app/PageRevision.php b/app/PageRevision.php index f1b4bc587..c258913ff 100644 --- a/app/PageRevision.php +++ b/app/PageRevision.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model; class PageRevision extends Model { - protected $fillable = ['name', 'html', 'text']; + protected $fillable = ['name', 'html', 'text', 'markdown']; /** * Get the user that created the page revision diff --git a/app/Permission.php b/app/Permission.php index 794df01ab..a146dcf63 100644 --- a/app/Permission.php +++ b/app/Permission.php @@ -11,7 +11,7 @@ class Permission extends Model */ public function roles() { - return $this->belongsToMany('BookStack\Permissions'); + return $this->belongsToMany('BookStack\Role'); } /** diff --git a/app/Providers/LdapUserProvider.php b/app/Providers/LdapUserProvider.php index 30fa739c2..a15257aec 100644 --- a/app/Providers/LdapUserProvider.php +++ b/app/Providers/LdapUserProvider.php @@ -115,7 +115,7 @@ class LdapUserProvider implements UserProvider $model->name = $userDetails['name']; $model->external_auth_id = $userDetails['uid']; $model->email = $userDetails['email']; - $model->email_confirmed = true; + $model->email_confirmed = false; return $model; } diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index f9f31a7aa..530f550b1 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -136,6 +136,18 @@ class ChapterRepo extends EntityRepo return $slug; } + /** + * Get a new priority value for a new page to be added + * to the given chapter. + * @param Chapter $chapter + * @return int + */ + public function getNewPriority(Chapter $chapter) + { + $lastPage = $chapter->pages->last(); + return $lastPage !== null ? $lastPage->priority + 1 : 0; + } + /** * Get chapters by the given search term. * @param string $term diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index 1a798e53b..cb3dd6674 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -84,7 +84,7 @@ class EntityRepo if ($additionalQuery !== false && is_callable($additionalQuery)) { $additionalQuery($query); } - return $query->skip($page * $count)->take($count)->get(); + return $query->with('book')->skip($page * $count)->take($count)->get(); } /** @@ -114,7 +114,7 @@ class EntityRepo { return $this->restrictionService->enforcePageRestrictions($this->page) ->where('draft', '=', false) - ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get(); + ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get(); } /** diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php index 2e2624a6e..8dd4d346d 100644 --- a/app/Repos/ImageRepo.php +++ b/app/Repos/ImageRepo.php @@ -2,6 +2,7 @@ use BookStack\Image; +use BookStack\Page; use BookStack\Services\ImageService; use BookStack\Services\RestrictionService; use Setting; @@ -13,18 +14,21 @@ class ImageRepo protected $image; protected $imageService; protected $restictionService; + protected $page; /** * ImageRepo constructor. * @param Image $image * @param ImageService $imageService * @param RestrictionService $restrictionService + * @param Page $page */ - public function __construct(Image $image, ImageService $imageService, RestrictionService $restrictionService) + public function __construct(Image $image, ImageService $imageService, RestrictionService $restrictionService, Page $page) { $this->image = $image; $this->imageService = $imageService; $this->restictionService = $restrictionService; + $this->page = $page; } @@ -38,6 +42,31 @@ class ImageRepo return $this->image->findOrFail($id); } + /** + * Execute a paginated query, returning in a standard format. + * Also runs the query through the restriction system. + * @param $query + * @param int $page + * @param int $pageSize + * @return array + */ + private function returnPaginated($query, $page = 0, $pageSize = 24) + { + $images = $this->restictionService->filterRelatedPages($query, 'images', 'uploaded_to'); + $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); + $hasMore = count($images) > $pageSize; + + $returnImages = $images->take(24); + $returnImages->each(function ($image) { + $this->loadThumbs($image); + }); + + return [ + 'images' => $returnImages, + 'hasMore' => $hasMore + ]; + } + /** * Gets a load images paginated, filtered by image type. * @param string $type @@ -54,19 +83,46 @@ class ImageRepo $images = $images->where('created_by', '=', $userFilter); } - $images = $this->restictionService->filterRelatedPages($images, 'images', 'uploaded_to'); - $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); - $hasMore = count($images) > $pageSize; + return $this->returnPaginated($images, $page, $pageSize); + } - $returnImages = $images->take(24); - $returnImages->each(function ($image) { - $this->loadThumbs($image); - }); + /** + * Search for images by query, of a particular type. + * @param string $type + * @param int $page + * @param int $pageSize + * @param string $searchTerm + * @return array + */ + public function searchPaginatedByType($type, $page = 0, $pageSize = 24, $searchTerm) + { + $images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%'); + return $this->returnPaginated($images, $page, $pageSize); + } - return [ - 'images' => $returnImages, - 'hasMore' => $hasMore - ]; + /** + * Get gallery images with a particular filter criteria such as + * being within the current book or page. + * @param int $pagination + * @param int $pageSize + * @param $filter + * @param $pageId + * @return array + */ + public function getGalleryFiltered($pagination = 0, $pageSize = 24, $filter, $pageId) + { + $images = $this->image->where('type', '=', 'gallery'); + + $page = $this->page->findOrFail($pageId); + + if ($filter === 'page') { + $images = $images->where('uploaded_to', '=', $page->id); + } elseif ($filter === 'book') { + $validPageIds = $page->book->pages->pluck('id')->toArray(); + $images = $images->whereIn('uploaded_to', $validPageIds); + } + + return $this->returnPaginated($images, $pagination, $pageSize); } /** diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index 4c3512fa7..9a7502754 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -312,6 +312,7 @@ class PageRepo extends EntityRepo $page->fill($input); $page->html = $this->formatHtml($input['html']); $page->text = strip_tags($page->html); + if (setting('app-editor') !== 'markdown') $page->markdown = ''; $page->updated_by = $userId; $page->save(); @@ -348,6 +349,7 @@ class PageRepo extends EntityRepo public function saveRevision(Page $page) { $revision = $this->pageRevision->fill($page->toArray()); + if (setting('app-editor') !== 'markdown') $revision->markdown = ''; $revision->page_id = $page->id; $revision->slug = $page->slug; $revision->book_slug = $page->book->slug; @@ -386,6 +388,8 @@ class PageRepo extends EntityRepo } $draft->fill($data); + if (setting('app-editor') !== 'markdown') $draft->markdown = ''; + $draft->save(); return $draft; } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index d5a4b1503..9b5c8d7e7 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -106,7 +106,8 @@ class UserRepo return $this->user->forceCreate([ 'name' => $data['name'], 'email' => $data['email'], - 'password' => bcrypt($data['password']) + 'password' => bcrypt($data['password']), + 'email_confirmed' => false ]); } diff --git a/app/Role.php b/app/Role.php index 270e4e0b8..4e14db181 100644 --- a/app/Role.php +++ b/app/Role.php @@ -43,6 +43,15 @@ class Role extends Model $this->permissions()->attach($permission->id); } + /** + * Detach a single permission from this role. + * @param Permission $permission + */ + public function detachPermission(Permission $permission) + { + $this->permissions()->detach($permission->id); + } + /** * Get the role object for the specified role. * @param $roleName diff --git a/app/Services/SettingService.php b/app/Services/SettingService.php index bcc7eae31..bf5fa918e 100644 --- a/app/Services/SettingService.php +++ b/app/Services/SettingService.php @@ -44,28 +44,39 @@ class SettingService /** * Gets a setting value from the cache or database. + * Looks at the system defaults if not cached or in database. * @param $key * @param $default * @return mixed */ protected function getValueFromStore($key, $default) { + // Check for an overriding value $overrideValue = $this->getOverrideValue($key); if ($overrideValue !== null) return $overrideValue; + // Check the cache $cacheKey = $this->cachePrefix . $key; if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } + // Check the database $settingObject = $this->getSettingObjectByKey($key); - if ($settingObject !== null) { $value = $settingObject->value; $this->cache->forever($cacheKey, $value); return $value; } + // Check the defaults set in the app config. + $configPrefix = 'setting-defaults.' . $key; + if (config()->has($configPrefix)) { + $value = config($configPrefix); + $this->cache->forever($cacheKey, $value); + return $value; + } + return $default; } diff --git a/app/Services/ViewService.php b/app/Services/ViewService.php index 75ffd21dc..6b50e90de 100644 --- a/app/Services/ViewService.php +++ b/app/Services/ViewService.php @@ -1,6 +1,5 @@ restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type') - ->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) + ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) ->groupBy('viewable_id', 'viewable_type') ->orderBy('view_count', 'desc'); if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); - $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); - $viewedEntities = $views->map(function ($item) { - return $item->viewable()->getResults(); - }); - return $viewedEntities; + return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable'); } /** @@ -81,21 +75,18 @@ class ViewService public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) { if ($this->user === null) return collect(); - $skipCount = $count * $page; + $query = $this->restrictionService ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); $query = $query->where('user_id', '=', auth()->user()->id); - $views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get(); - $viewedEntities = $views->map(function ($item) { - return $item->viewable; - }); - return $viewedEntities; + $viewables = $query->with('viewable')->orderBy('updated_at', 'desc') + ->skip($count * $page)->take($count)->get()->pluck('viewable'); + return $viewables; } - /** * Reset all view counts by deleting all views. */ @@ -104,5 +95,4 @@ class ViewService $this->view->truncate(); } - } \ No newline at end of file diff --git a/composer.json b/composer.json index 4a4c554a7..8f375a279 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "barryvdh/laravel-ide-helper": "^2.1", "barryvdh/laravel-debugbar": "^2.0", "league/flysystem-aws-s3-v3": "^1.0", - "barryvdh/laravel-dompdf": "0.6.*" + "barryvdh/laravel-dompdf": "0.6.*", + "predis/predis": "^1.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index 9951362c1..63d378753 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "523e654de96df9259fa5dfcb583d6e3e", - "content-hash": "74b5601c253aab71cf55e0885f31ae7f", + "hash": "eb7c71e9ed116d3fd2a1d0af07f9f134", + "content-hash": "17d2d7fc5fed682f2a290d6588538035", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.15.1", + "version": "3.17.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5e6078913293576de969703481994b77c380ca30" + "reference": "1cef9b334729b3564c9aef15481a55561c54b53f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e6078913293576de969703481994b77c380ca30", - "reference": "5e6078913293576de969703481994b77c380ca30", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1cef9b334729b3564c9aef15481a55561c54b53f", + "reference": "1cef9b334729b3564c9aef15481a55561c54b53f", "shasum": "" }, "require": { @@ -85,27 +85,27 @@ "s3", "sdk" ], - "time": "2016-02-11 23:23:31" + "time": "2016-04-07 22:44:13" }, { "name": "barryvdh/laravel-debugbar", - "version": "v2.1.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "974fd16e328ca851a081449100d9509af59cf0ff" + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/974fd16e328ca851a081449100d9509af59cf0ff", - "reference": "974fd16e328ca851a081449100d9509af59cf0ff", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/13b7058d2120c8d5af7f1ada21b7c44dd87b666a", + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a", "shasum": "" }, "require": { - "illuminate/support": "~5.0.17|5.1.*|5.2.*", + "illuminate/support": "5.1.*|5.2.*", "maximebf/debugbar": "~1.11.0", - "php": ">=5.4.0", - "symfony/finder": "~2.6|~3.0" + "php": ">=5.5.9", + "symfony/finder": "~2.7|~3.0" }, "type": "library", "extra": { @@ -139,7 +139,7 @@ "profiler", "webprofiler" ], - "time": "2015-12-22 06:22:38" + "time": "2016-02-17 08:32:21" }, { "name": "barryvdh/laravel-dompdf", @@ -191,16 +191,16 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.1.2", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772" + "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/f1ebd847aac9a4545325d35108cafc285fe1605f", + "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f", "shasum": "" }, "require": { @@ -208,8 +208,8 @@ "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", "illuminate/support": "5.0.x|5.1.x|5.2.x", "php": ">=5.4.0", - "phpdocumentor/reflection-docblock": "2.0.4", - "symfony/class-loader": "~2.3" + "phpdocumentor/reflection-docblock": "^2.0.4", + "symfony/class-loader": "~2.3|~3.0" }, "require-dev": { "doctrine/dbal": "~2.3" @@ -250,7 +250,7 @@ "phpstorm", "sublime" ], - "time": "2015-12-21 19:48:06" + "time": "2016-03-03 08:45:00" }, { "name": "classpreloader/classpreloader", @@ -541,16 +541,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.1.1", + "version": "6.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" + "reference": "d094e337976dff9d8e2424e8485872194e768662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", "shasum": "" }, "require": { @@ -566,7 +566,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -599,20 +599,20 @@ "rest", "web service" ], - "time": "2015-11-23 00:47:50" + "time": "2016-03-21 20:02:09" }, { "name": "guzzlehttp/promises", - "version": "1.0.3", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8", "shasum": "" }, "require": { @@ -650,20 +650,20 @@ "keywords": [ "promise" ], - "time": "2015-10-15 22:28:00" + "time": "2016-03-08 01:15:46" }, { "name": "guzzlehttp/psr7", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb" + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb", - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b", + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b", "shasum": "" }, "require": { @@ -708,20 +708,20 @@ "stream", "uri" ], - "time": "2016-01-23 01:23:02" + "time": "2016-02-18 21:54:00" }, { "name": "intervention/image", - "version": "2.3.5", + "version": "2.3.6", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89" + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/9f29360b8ab94585cb9e80cf9045abd5b85feb89", - "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89", + "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6", + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6", "shasum": "" }, "require": { @@ -770,7 +770,7 @@ "thumbnail", "watermark" ], - "time": "2016-01-02 19:15:13" + "time": "2016-02-26 18:18:19" }, { "name": "jakub-onderka/php-console-color", @@ -919,16 +919,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.16", + "version": "v5.2.29", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "39e89553c124dce266da03ee3c0260bdd62f1848" + "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/39e89553c124dce266da03ee3c0260bdd62f1848", - "reference": "39e89553c124dce266da03ee3c0260bdd62f1848", + "url": "https://api.github.com/repos/laravel/framework/zipball/e3d644eb131f18c5f3d28ff7bc678bc797091f20", + "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20", "shasum": "" }, "require": { @@ -941,9 +941,9 @@ "monolog/monolog": "~1.11", "mtdowling/cron-expression": "~1.0", "nesbot/carbon": "~1.20", - "paragonie/random_compat": "~1.1", + "paragonie/random_compat": "~1.4", "php": ">=5.5.9", - "psy/psysh": "0.6.*", + "psy/psysh": "0.7.*", "swiftmailer/swiftmailer": "~5.1", "symfony/console": "2.8.*|3.0.*", "symfony/debug": "2.8.*|3.0.*", @@ -1043,20 +1043,20 @@ "framework", "laravel" ], - "time": "2016-02-15 17:46:58" + "time": "2016-04-03 01:43:55" }, { "name": "laravel/socialite", - "version": "v2.0.14", + "version": "v2.0.15", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "b15f4be0ac739405120d74b837af423aa71502d9" + "reference": "edd00ab96933e3ef053533cce81e958fb26921af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/b15f4be0ac739405120d74b837af423aa71502d9", - "reference": "b15f4be0ac739405120d74b837af423aa71502d9", + "url": "https://api.github.com/repos/laravel/socialite/zipball/edd00ab96933e3ef053533cce81e958fb26921af", + "reference": "edd00ab96933e3ef053533cce81e958fb26921af", "shasum": "" }, "require": { @@ -1097,20 +1097,20 @@ "laravel", "oauth" ], - "time": "2015-10-16 15:39:46" + "time": "2016-03-21 14:30:30" }, { "name": "league/flysystem", - "version": "1.0.16", + "version": "1.0.20", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", "shasum": "" }, "require": { @@ -1123,8 +1123,7 @@ "ext-fileinfo": "*", "mockery/mockery": "~0.9", "phpspec/phpspec": "^2.2", - "phpspec/prophecy-phpunit": "~1.0", - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "~4.8 || ~5.0" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -1181,7 +1180,7 @@ "sftp", "storage" ], - "time": "2015-12-19 20:16:43" + "time": "2016-03-14 21:54:11" }, { "name": "league/flysystem-aws-s3-v3", @@ -1295,16 +1294,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.11.0", + "version": "v1.11.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "07741d84d39d10f00551c94284cdefcc69703e77" + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77", - "reference": "07741d84d39d10f00551c94284cdefcc69703e77", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f", + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f", "shasum": "" }, "require": { @@ -1352,20 +1351,20 @@ "debug", "debugbar" ], - "time": "2015-12-10 09:50:24" + "time": "2016-01-22 12:22:23" }, { "name": "monolog/monolog", - "version": "1.17.2", + "version": "1.18.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" + "reference": "064b38c16790249488e7a8b987acf1c9d7383c09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/064b38c16790249488e7a8b987acf1c9d7383c09", + "reference": "064b38c16790249488e7a8b987acf1c9d7383c09", "shasum": "" }, "require": { @@ -1380,13 +1379,13 @@ "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", "phpunit/phpunit": "~4.5", "phpunit/phpunit-mock-objects": "2.3.0", "raven/raven": "^0.13", "ruflin/elastica": ">=0.90 <3.0", - "swiftmailer/swiftmailer": "~5.3", - "videlalvaro/php-amqplib": "~2.4" + "swiftmailer/swiftmailer": "~5.3" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -1394,16 +1393,17 @@ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongo": "Allow sending log messages to a MongoDB server", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "raven/raven": "Allow sending log messages to a Sentry server", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.16.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1429,7 +1429,7 @@ "logging", "psr-3" ], - "time": "2015-10-14 12:51:02" + "time": "2016-04-02 13:12:58" }, { "name": "mtdowling/cron-expression", @@ -1579,16 +1579,16 @@ }, { "name": "nikic/php-parser", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01" + "reference": "ce5be709d59b32dd8a88c80259028759991a4206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c542e5d86a9775abd1021618eb2430278bfc1e01", - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce5be709d59b32dd8a88c80259028759991a4206", + "reference": "ce5be709d59b32dd8a88c80259028759991a4206", "shasum": "" }, "require": { @@ -1626,20 +1626,20 @@ "parser", "php" ], - "time": "2015-12-04 15:28:43" + "time": "2016-02-28 19:48:28" }, { "name": "paragonie/random_compat", - "version": "v1.2.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "b0e69d10852716b2ccbdff69c75c477637220790" + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790", - "reference": "b0e69d10852716b2ccbdff69c75c477637220790", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774", + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774", "shasum": "" }, "require": { @@ -1674,7 +1674,7 @@ "pseudorandom", "random" ], - "time": "2016-02-06 03:52:05" + "time": "2016-03-18 20:34:03" }, { "name": "phenx/php-font-lib", @@ -1759,6 +1759,56 @@ ], "time": "2015-02-03 12:10:50" }, + { + "name": "predis/predis", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04", + "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete PHP client library for Redis", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2015-07-30 18:34:15" + }, { "name": "psr/http-message", "version": "1.0", @@ -1848,16 +1898,16 @@ }, { "name": "psy/psysh", - "version": "v0.6.1", + "version": "v0.7.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed" + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0f04df0b23663799a8941fae13cd8e6299bde3ed", - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", "shasum": "" }, "require": { @@ -1886,7 +1936,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.7.x-dev" + "dev-develop": "0.8.x-dev" } }, "autoload": { @@ -1916,7 +1966,7 @@ "interactive", "shell" ], - "time": "2015-11-12 16:18:56" + "time": "2016-03-09 05:03:14" }, { "name": "swiftmailer/swiftmailer", @@ -1973,28 +2023,32 @@ }, { "name": "symfony/class-loader", - "version": "v2.8.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269" + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/98e9089a428ed0e39423b67352c57ef5910a3269", - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "require-dev": { - "symfony/finder": "~2.0,>=2.0.5|~3.0.0" + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2021,20 +2075,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2016-01-03 15:33:41" + "time": "2016-03-30 10:41:14" }, { "name": "symfony/console", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0" + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", - "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", + "url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd", + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd", "shasum": "" }, "require": { @@ -2081,20 +2135,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-03-16 17:00:50" }, { "name": "symfony/debug", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "29606049ced1ec715475f88d1bbe587252a3476e" + "reference": "a06d10888a45afd97534506afb058ec38d9ba35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e", - "reference": "29606049ced1ec715475f88d1bbe587252a3476e", + "url": "https://api.github.com/repos/symfony/debug/zipball/a06d10888a45afd97534506afb058ec38d9ba35b", + "reference": "a06d10888a45afd97534506afb058ec38d9ba35b", "shasum": "" }, "require": { @@ -2138,20 +2192,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-01-27 05:14:46" + "time": "2016-03-30 10:41:14" }, { "name": "symfony/event-dispatcher", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa" + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", - "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9002dcf018d884d294b1ef20a6f968efc1128f39", + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39", "shasum": "" }, "require": { @@ -2198,20 +2252,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-01-27 05:14:46" + "time": "2016-03-10 10:34:12" }, { "name": "symfony/finder", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", + "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52", + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52", "shasum": "" }, "require": { @@ -2247,24 +2301,25 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-01-27 05:14:46" + "time": "2016-03-10 11:13:05" }, { "name": "symfony/http-foundation", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d" + "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9344a87ceedfc50354a39653e54257ee9aa6a77d", - "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/99f38445a874e7becb8afc4b4a79ee181cf6ec3f", + "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "symfony/expression-language": "~2.8|~3.0" @@ -2299,20 +2354,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-03-27 14:50:32" }, { "name": "symfony/http-kernel", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "cec02604450481ac26710ca4249cc61b57b23942" + "reference": "579f828489659d7b3430f4bd9b67b4618b387dea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cec02604450481ac26710ca4249cc61b57b23942", - "reference": "cec02604450481ac26710ca4249cc61b57b23942", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/579f828489659d7b3430f4bd9b67b4618b387dea", + "reference": "579f828489659d7b3430f4bd9b67b4618b387dea", "shasum": "" }, "require": { @@ -2381,11 +2436,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-02-03 12:38:44" + "time": "2016-03-25 01:41:20" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2444,7 +2499,7 @@ }, { "name": "symfony/polyfill-php56", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", @@ -2500,7 +2555,7 @@ }, { "name": "symfony/polyfill-util", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", @@ -2552,16 +2607,16 @@ }, { "name": "symfony/process", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "dfecef47506179db2501430e732adbf3793099c8" + "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8", - "reference": "dfecef47506179db2501430e732adbf3793099c8", + "url": "https://api.github.com/repos/symfony/process/zipball/e6f1f98bbd355d209a992bfff45e7edfbd4a0776", + "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776", "shasum": "" }, "require": { @@ -2597,20 +2652,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-03-30 10:41:14" }, { "name": "symfony/routing", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166" + "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4686baa55a835e1c1ede9b86ba02415c8c8d6166", - "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166", + "url": "https://api.github.com/repos/symfony/routing/zipball/d061b609f2d0769494c381ec92f5c5cc5e4a20aa", + "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa", "shasum": "" }, "require": { @@ -2633,6 +2688,7 @@ "symfony/config": "For using the all-in-one router or any loader", "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" }, "type": "library", @@ -2671,20 +2727,20 @@ "uri", "url" ], - "time": "2016-01-27 05:14:46" + "time": "2016-03-23 13:23:25" }, { "name": "symfony/translation", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91" + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", - "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", + "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2", + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2", "shasum": "" }, "require": { @@ -2735,20 +2791,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-03-25 01:41:20" }, { "name": "symfony/var-dumper", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3" + "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/24bb94807eff00db49374c37ebf56a0304e8aef3", - "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3841ed86527d18ee2c35fe4afb1b2fc60f8fae79", + "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79", "shasum": "" }, "require": { @@ -2798,7 +2854,7 @@ "debug", "dump" ], - "time": "2016-01-07 13:38:51" + "time": "2016-03-10 10:34:12" }, { "name": "vlucas/phpdotenv", @@ -3105,16 +3161,16 @@ }, { "name": "phpspec/phpspec", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" + "reference": "385ecb015e97c13818074f1517928b24d4a26067" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067", + "reference": "385ecb015e97c13818074f1517928b24d4a26067", "shasum": "" }, "require": { @@ -3179,7 +3235,7 @@ "testing", "tests" ], - "time": "2016-01-01 10:17:54" + "time": "2016-03-20 20:34:32" }, { "name": "phpspec/prophecy", @@ -3485,16 +3541,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.23", + "version": "4.8.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" + "reference": "a1066c562c52900a142a0e2bbf0582994671385e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e", + "reference": "a1066c562c52900a142a0e2bbf0582994671385e", "shasum": "" }, "require": { @@ -3553,7 +3609,7 @@ "testing", "xunit" ], - "time": "2016-02-11 14:56:33" + "time": "2016-03-14 06:16:08" }, { "name": "phpunit/phpunit-mock-objects", @@ -3729,16 +3785,16 @@ }, { "name": "sebastian/environment", - "version": "1.3.3", + "version": "1.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6e7133793a8e5a5714a551a8324337374be209df" + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", - "reference": "6e7133793a8e5a5714a551a8324337374be209df", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", "shasum": "" }, "require": { @@ -3775,7 +3831,7 @@ "environment", "hhvm" ], - "time": "2015-12-02 08:37:27" + "time": "2016-02-26 18:40:46" }, { "name": "sebastian/exporter", @@ -3984,16 +4040,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "6605602690578496091ac20ec7a5cbd160d4dff4" + "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4", - "reference": "6605602690578496091ac20ec7a5cbd160d4dff4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0", + "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0", "shasum": "" }, "require": { @@ -4033,20 +4089,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2016-01-27 05:14:46" + "time": "2016-03-04 07:55:57" }, { "name": "symfony/dom-crawler", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d" + "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/18a06d7a9af41718c20764a674a0ebba3bc40d1f", + "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f", "shasum": "" }, "require": { @@ -4089,20 +4145,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-01-25 09:56:57" + "time": "2016-03-23 13:23:25" }, { "name": "symfony/yaml", - "version": "v3.0.2", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" + "reference": "0047c8366744a16de7516622c5b7355336afae96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", + "reference": "0047c8366744a16de7516622c5b7355336afae96", "shasum": "" }, "require": { @@ -4138,7 +4194,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-03-04 07:55:57" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index 650ad1d07..d305af3c0 100644 --- a/config/app.php +++ b/config/app.php @@ -5,6 +5,8 @@ return [ 'env' => env('APP_ENV', 'production'), + 'editor' => env('APP_EDITOR', 'html'), + /* |-------------------------------------------------------------------------- | Application Debug Mode diff --git a/config/cache.php b/config/cache.php index 076a0299f..0a9568322 100644 --- a/config/cache.php +++ b/config/cache.php @@ -6,9 +6,8 @@ if (env('CACHE_DRIVER') === 'memcached') { $memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ',')); foreach ($memcachedServers as $index => $memcachedServer) { $memcachedServerDetails = explode(':', $memcachedServer); - $components = count($memcachedServerDetails); - if ($components < 2) $memcachedServerDetails[] = '11211'; - if ($components < 3) $memcachedServerDetails[] = '100'; + if (count($memcachedServerDetails) < 2) $memcachedServerDetails[] = '11211'; + if (count($memcachedServerDetails) < 3) $memcachedServerDetails[] = '100'; $memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails); } } @@ -83,6 +82,6 @@ return [ | */ - 'prefix' => 'laravel', + 'prefix' => env('CACHE_PREFIX', 'bookstack'), ]; diff --git a/config/database.php b/config/database.php index 9650de117..20d461fc9 100644 --- a/config/database.php +++ b/config/database.php @@ -1,5 +1,21 @@ env('REDIS_CLUSTER', false) + ]; + foreach ($redisServers as $index => $redisServer) { + $redisServerName = ($index === 0) ? 'default' : 'redis-server-' . $index; + $redisServerDetails = explode(':', $redisServer); + if (count($redisServerDetails) < 2) $redisServerDetails[] = '6379'; + if (count($redisServerDetails) < 3) $redisServerDetails[] = '0'; + $redisConfig[$redisServerName] = array_combine($redisServerKeys, $redisServerDetails); + } +} + return [ /* @@ -123,16 +139,6 @@ return [ | */ - 'redis' => [ - - 'cluster' => false, - - 'default' => [ - 'host' => '127.0.0.1', - 'port' => 6379, - 'database' => 0, - ], - - ], + 'redis' => $redisConfig, ]; diff --git a/config/setting-defaults.php b/config/setting-defaults.php new file mode 100644 index 000000000..17bae1848 --- /dev/null +++ b/config/setting-defaults.php @@ -0,0 +1,10 @@ + 'wysiwyg' + +]; \ No newline at end of file diff --git a/database/migrations/2016_03_25_123157_add_markdown_support.php b/database/migrations/2016_03_25_123157_add_markdown_support.php new file mode 100644 index 000000000..2daa32cfb --- /dev/null +++ b/database/migrations/2016_03_25_123157_add_markdown_support.php @@ -0,0 +1,39 @@ +longText('markdown')->default(''); + }); + + Schema::table('page_revisions', function (Blueprint $table) { + $table->longText('markdown')->default(''); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn('markdown'); + }); + + Schema::table('page_revisions', function (Blueprint $table) { + $table->dropColumn('markdown'); + }); + } +} diff --git a/package.json b/package.json index a1fb06b1c..866109c2a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "bootstrap-sass": "^3.0.0", "dropzone": "^4.0.1", "laravel-elixir": "^3.4.0", + "marked": "^0.3.5", + "moment": "^2.12.0", "zeroclipboard": "^2.2.0" } } diff --git a/public/fonts/roboto-mono-v4-latin-regular.woff b/public/fonts/roboto-mono-v4-latin-regular.woff new file mode 100644 index 000000000..8cb9e6fd8 Binary files /dev/null and b/public/fonts/roboto-mono-v4-latin-regular.woff differ diff --git a/public/fonts/roboto-mono-v4-latin-regular.woff2 b/public/fonts/roboto-mono-v4-latin-regular.woff2 new file mode 100644 index 000000000..1f6598111 Binary files /dev/null and b/public/fonts/roboto-mono-v4-latin-regular.woff2 differ diff --git a/readme.md b/readme.md index 0730e3de3..067983e84 100644 --- a/readme.md +++ b/readme.md @@ -1,149 +1,18 @@ # BookStack -A platform to create documentation/wiki content. General information about BookStack can be found at https://www.bookstackapp.com/ +A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/. -1. [Requirements](#requirements) -2. [Installation](#installation) - - [Server Rewrite Rules](#url-rewrite-rules) -3. [Updating](#updating-bookstack) -4. [Social Authentication](#social-authentication) - - [Google](#google) - - [GitHub](#github) -5. [LDAP Authentication](#ldap-authentication) -6. [Testing](#testing) -7. [License](#license) -8. [Attribution](#attribution) - - -## Requirements - -BookStack has similar requirements to Laravel: - -* PHP >= 5.5.9, Will need to be usable from the command line. -* PHP Extensions: `OpenSSL`, `PDO`, `MBstring`, `Tokenizer`, `GD` -* MySQL >= 5.6 -* Git (Not strictly required but helps manage updates) -* [Composer](https://getcomposer.org/) - -## Installation - -Ensure the above requirements are met before installing. Currently BookStack requires its own domain/subdomain and will not work in a site subdirectory. - -This project currently uses the `release` branch of this repository as a stable channel for providing updates. - -The installation is currently somewhat complicated and will be made simpler in future releases. Some PHP/Laravel experience will currently benefit. - -1. Clone the release branch of this repository into a folder. - -``` -git clone https://github.com/ssddanbrown/BookStack.git --branch release --single-branch -``` - -2. `cd` into the application folder and run `composer install`. -3. Copy the `.env.example` file to `.env` and fill with your own database and mail details. -4. Ensure the `storage`, `bootstrap/cache` & `public/uploads` folders are writable by the web server. -5. In the application root, Run `php artisan key:generate` to generate a unique application key. -6. If not using apache or if `.htaccess` files are disabled you will have to create some URL rewrite rules as shown below. -7. Run `php artisan migrate` to update the database. -8. Done! You can now login using the default admin details `admin@admin.com` with a password of `password`. It is recommended to change these details directly after first logging in. - -#### URL Rewrite rules - -**Apache** -``` -Options +FollowSymLinks -RewriteEngine On - -RewriteCond %{REQUEST_FILENAME} !-d -RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^ index.php [L] -``` - -**Nginx** -``` -location / { - try_files $uri $uri/ /index.php?$query_string; -} -``` -## Updating BookStack - -To update BookStack you can run the following command in the root directory of the application: -``` -git pull origin release && composer install && php artisan migrate -``` -This command will update the repository that was created in the installation, install the PHP dependencies using `composer` then run the database migrations. - -## Social Authentication - -BookStack currently supports login via both Google and GitHub. Once enabled options for these services will show up in the login, registration and user profile pages. By default these services are disabled. To enable them you will have to create an application on the external services to obtain the require application id's and secrets. Here are instructions to do this for the current supported services: - -### Google - -1. Open the [Google Developers Console](https://console.developers.google.com/). -2. Create a new project (May have to wait a short while for it to be created). -3. Select 'Enable and manage APIs'. -4. Enable the 'Google+ API'. -5. In 'Credentials' choose the 'OAuth consent screen' tab and enter a product name ('BookStack' or your custom set name). -6. Back in the 'Credentials' tab click 'New credentials' > 'OAuth client ID'. -7. Choose an application type of 'Web application' and enter the following urls under 'Authorized redirect URIs', changing `https://example.com` to your own domain where BookStack is hosted: - - `https://example.com/login/service/google/callback` - - `https://example.com/register/service/google/callback` -8. Click 'Create' and your app_id and secret will be displayed. Replace the false value on both the `GOOGLE_APP_ID` & `GOOGLE_APP_SECRET` variables in the '.env' file in the BookStack root directory with your own app_id and secret. -9. Set the 'APP_URL' environment variable to be the same domain as you entered in step 7. So, in this example, it will be `https://example.com`. -10. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Google accounts. - -### Github - -1. While logged in, open up your [GitHub developer applications](https://github.com/settings/developers). -2. Click 'Register new application'. -3. Enter an application name ('BookStack' or your custom set name), A link to your app instance under 'Homepage URL' and an 'Authorization callback URL' of the url that your BookStack instance is hosted on then click 'Register application'. -4. A 'Client ID' and a 'Client Secret' value will be shown. Add these two values to the to the `GITHUB_APP_ID` and `GITHUB_APP_SECRET` variables, replacing the default false value, in the '.env' file found in the BookStack root folder. -5. Set the 'APP_URL' environment variable to be the same domain as you entered in step 3. -6. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Github account. - -## LDAP Authentication - -BookStack can be configured to allow LDAP based user login. While LDAP login is enabled you cannot log in with the standard user/password login and new user registration is disabled. BookStack will only use the LDAP server for getting user details and for authentication. Data on the LDAP server is not currently editable through BookStack. - -When a LDAP user logs into BookStack for the first time their BookStack profile will be created and they will be given the default role set under the 'Default user role after registration' option in the application settings. - -To set up LDAP-based authentication add or modify the following variables in your `.env` file: - -``` -# General auth -AUTH_METHOD=ldap - -# The LDAP host, Adding a port is optional -LDAP_SERVER=ldap://example.com:389 - -# The base DN from where users will be searched within. -LDAP_BASE_DN=ou=People,dc=example,dc=com - -# The full DN and password of the user used to search the server -# Can both be left as false to bind anonymously -LDAP_DN=false -LDAP_PASS=false - -# A filter to use when searching for users -# The user-provided user-name used to replace any occurrences of '${user}' -LDAP_USER_FILTER=(&(uid=${user})) - -# Set the LDAP version to use when connecting to the server. -LDAP_VERSION=false -``` - -You will also need to have the php-ldap extension installed on your system. It's recommended to change your `APP_DEBUG` variable to `true` while setting up LDAP to make any errors visible. Remember to change this back after LDAP is functioning. - -A user in BookStack will be linked to a LDAP user via a 'uid'. If a LDAP user uid changes it can be updated in BookStack by an admin by changing the 'External Authentication ID' field on the user's profile. - -You may find that you cannot log in with your initial Admin account after changing the `AUTH_METHOD` to `ldap`. To get around this set the `AUTH_METHOD` to `standard`, login with your admin account then change it back to `ldap`. You get then edit your profile and add your LDAP uid under the 'External Authentication ID' field. You will then be able to login in with that ID. +* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation) +* [Documentation](https://www.bookstackapp.com/docs) +* [Demo Instance](https://demo.bookstackapp.com) *(Login username: `admin@example.com`. Password: `password`)* +* [BookStack Blog](https://www.bookstackapp.com/blog) ## Development & Testing All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at it's version. Here are the current development requirements: -* [Node.js](https://nodejs.org/en/) **Development Only** -* [Gulp](http://gulpjs.com/) **Development Only** +* [Node.js](https://nodejs.org/en/) +* [Gulp](http://gulpjs.com/) SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp. @@ -176,3 +45,4 @@ These are the great projects used to help build BookStack: * [Dropzone.js](http://www.dropzonejs.com/) * [ZeroClipboard](http://zeroclipboard.org/) * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html) +* [Marked](https://github.com/chjj/marked) diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index 29a448265..8b3d952be 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -1,5 +1,7 @@ "use strict"; +var moment = require('moment'); + module.exports = function (ngApp, events) { ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService', @@ -14,20 +16,40 @@ module.exports = function (ngApp, events) { $scope.imageUpdateSuccess = false; $scope.imageDeleteSuccess = false; $scope.uploadedTo = $attrs.uploadedTo; + $scope.view = 'all'; + + $scope.searching = false; + $scope.searchTerm = ''; var page = 0; var previousClickTime = 0; + var previousClickImage = 0; var dataLoaded = false; var callback = false; + var preSearchImages = []; + var preSearchHasMore = false; + /** - * Simple returns the appropriate upload url depending on the image type set. + * Used by dropzone to get the endpoint to upload to. * @returns {string} */ $scope.getUploadUrl = function () { return '/images/' + $scope.imageType + '/upload'; }; + /** + * Cancel the current search operation. + */ + function cancelSearch() { + $scope.searching = false; + $scope.searchTerm = ''; + $scope.images = preSearchImages; + $scope.hasMore = preSearchHasMore; + } + $scope.cancelSearch = cancelSearch; + + /** * Runs on image upload, Adds an image to local list of images * and shows a success message to the user. @@ -59,7 +81,7 @@ module.exports = function (ngApp, events) { var currentTime = Date.now(); var timeDiff = currentTime - previousClickTime; - if (timeDiff < dblClickTime) { + if (timeDiff < dblClickTime && image.id === previousClickImage) { // If double click callbackAndHide(image); } else { @@ -68,6 +90,7 @@ module.exports = function (ngApp, events) { $scope.dependantPages = false; } previousClickTime = currentTime; + previousClickImage = image.id; }; /** @@ -110,20 +133,69 @@ module.exports = function (ngApp, events) { $scope.showing = false; }; + var baseUrl = '/images/' + $scope.imageType + '/all/' + /** * Fetch the list image data from the server. */ function fetchData() { - var url = '/images/' + $scope.imageType + '/all/' + page; + var url = baseUrl + page + '?'; + var components = {}; + if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo; + if ($scope.searching) components['term'] = $scope.searchTerm; + + + var urlQueryString = Object.keys(components).map((key) => { + return key + '=' + encodeURIComponent(components[key]); + }).join('&'); + url += urlQueryString; + $http.get(url).then((response) => { $scope.images = $scope.images.concat(response.data.images); $scope.hasMore = response.data.hasMore; page++; }); } - $scope.fetchData = fetchData; + /** + * Start a search operation + * @param searchTerm + */ + $scope.searchImages = function() { + + if ($scope.searchTerm === '') { + cancelSearch(); + return; + } + + if (!$scope.searching) { + preSearchImages = $scope.images; + preSearchHasMore = $scope.hasMore; + } + + $scope.searching = true; + $scope.images = []; + $scope.hasMore = false; + page = 0; + baseUrl = '/images/' + $scope.imageType + '/search/'; + fetchData(); + }; + + /** + * Set the current image listing view. + * @param viewName + */ + $scope.setView = function(viewName) { + cancelSearch(); + $scope.images = []; + $scope.hasMore = false; + page = 0; + $scope.view = viewName; + baseUrl = '/images/' + $scope.imageType + '/' + viewName + '/'; + fetchData(); + } + /** * Save the details of an image. * @param event @@ -216,16 +288,20 @@ module.exports = function (ngApp, events) { }]); - ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', function ($scope, $http, $attrs, $interval, $timeout) { + ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce', + function ($scope, $http, $attrs, $interval, $timeout, $sce) { $scope.editorOptions = require('./pages/page-form'); - $scope.editorHtml = ''; + $scope.editContent = ''; $scope.draftText = ''; var pageId = Number($attrs.pageId); var isEdit = pageId !== 0; var autosaveFrequency = 30; // AutoSave interval in seconds. + var isMarkdown = $attrs.editorType === 'markdown'; $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1; $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1; + + // Set inital header draft text if ($scope.isUpdateDraft || $scope.isNewPageDraft) { $scope.draftText = 'Editing Draft' } else { @@ -245,7 +321,18 @@ module.exports = function (ngApp, events) { }, 1000); } - $scope.editorChange = function () {} + // Actions specifically for the markdown editor + if (isMarkdown) { + $scope.displayContent = ''; + // Editor change event + $scope.editorChange = function (content) { + $scope.displayContent = $sce.trustAsHtml(content); + } + } + + if (!isMarkdown) { + $scope.editorChange = function() {}; + } /** * Start the AutoSave loop, Checks for content change @@ -253,17 +340,18 @@ module.exports = function (ngApp, events) { */ function startAutoSave() { currentContent.title = $('#name').val(); - currentContent.html = $scope.editorHtml; + currentContent.html = $scope.editContent; autoSave = $interval(() => { var newTitle = $('#name').val(); - var newHtml = $scope.editorHtml; + var newHtml = $scope.editContent; if (newTitle !== currentContent.title || newHtml !== currentContent.html) { currentContent.html = newHtml; currentContent.title = newTitle; - saveDraft(newTitle, newHtml); + saveDraft(); } + }, 1000 * autosaveFrequency); } @@ -272,20 +360,23 @@ module.exports = function (ngApp, events) { * @param title * @param html */ - function saveDraft(title, html) { - $http.put('/ajax/page/' + pageId + '/save-draft', { - name: title, - html: html - }).then((responseData) => { - $scope.draftText = responseData.data.message; + function saveDraft() { + var data = { + name: $('#name').val(), + html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent + }; + + if (isMarkdown) data.markdown = $scope.editContent; + + $http.put('/ajax/page/' + pageId + '/save-draft', 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; }); } $scope.forceDraftSave = function() { - var newTitle = $('#name').val(); - var newHtml = $scope.editorHtml; - saveDraft(newTitle, newHtml); + saveDraft(); }; /** @@ -298,6 +389,7 @@ module.exports = function (ngApp, events) { $scope.draftText = 'Editing Page'; $scope.isUpdateDraft = false; $scope.$broadcast('html-update', responseData.data.html); + $scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html); $('#name').val(responseData.data.name); $timeout(() => { startAutoSave(); diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 71b35fb42..de87950dc 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -1,5 +1,6 @@ "use strict"; var DropZone = require('dropzone'); +var markdown = require('marked'); var toggleSwitchTemplate = require('./components/toggle-switch.html'); var imagePickerTemplate = require('./components/image-picker.html'); @@ -200,7 +201,82 @@ module.exports = function (ngApp, events) { tinymce.init(scope.tinymce); } } + }]); + + ngApp.directive('markdownInput', ['$timeout', function($timeout) { + return { + restrict: 'A', + scope: { + mdModel: '=', + mdChange: '=' + }, + link: function (scope, element, attrs) { + + // Set initial model content + var content = element.val(); + scope.mdModel = content; + scope.mdChange(markdown(content)); + + element.on('change input', (e) => { + content = element.val(); + $timeout(() => { + scope.mdModel = content; + scope.mdChange(markdown(content)); + }); + }); + + scope.$on('markdown-update', (event, value) => { + element.val(value); + scope.mdModel= value; + scope.mdChange(markdown(value)); + }); + + } + } + }]); + + 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"]'); + + var currentCaretPos = 0; + + input.blur((event) => { + currentCaretPos = input[0].selectionStart; + }); + + // Insert image shortcut + input.keydown((event) => { + if (event.which === 73 && event.ctrlKey && event.shiftKey) { + event.preventDefault(); + var caretPos = input[0].selectionStart; + var currentContent = input.val(); + var mdImageText = ""; + input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); + input.focus(); + input[0].selectionStart = caretPos + ("; + input[0].selectionEnd = caretPos + ('; + } + }); + + // Insert image from image manager + insertImage.click((event) => { + window.ImageManager.showExternal((image) => { + var caretPos = currentCaretPos; + var currentContent = input.val(); + var mdImageText = ""; + input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); + input.change(); + }); + }); + + } + } }]) - }; \ No newline at end of file diff --git a/resources/assets/sass/_fonts.scss b/resources/assets/sass/_fonts.scss index 0dc8c95b2..8cf677779 100644 --- a/resources/assets/sass/_fonts.scss +++ b/resources/assets/sass/_fonts.scss @@ -93,4 +93,15 @@ url('/fonts/roboto-regular-webfont.svg#robotoregular') format('svg'); font-weight: normal; font-style: normal; +} + +/* roboto-mono-regular - latin */ +// https://google-webfonts-helper.herokuapp.com +@font-face { + font-family: 'Roboto Mono'; + 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+ */ } \ No newline at end of file diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 037dad94a..4a505c5f8 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -26,6 +26,59 @@ display: none; } +#markdown-editor { + position: relative; + z-index: 5; + textarea { + font-family: 'Roboto Mono'; + font-style: normal; + font-weight: 400; + padding: $-xs $-m; + color: #444; + border-radius: 0; + max-height: 100%; + flex: 1; + border: 0; + width: 100%; + &:focus { + outline: 0; + } + } + .markdown-display, .markdown-editor-wrap { + flex: 1; + position: relative; + } + .markdown-editor-wrap { + display: flex; + flex-direction: column; + border: 1px solid #DDD; + } + .markdown-display { + padding: 0 $-m 0; + margin-left: -1px; + overflow-y: scroll; + .page-content { + margin: 0 auto; + } + } +} +.editor-toolbar { + width: 100%; + padding: $-xs $-m; + font-family: 'Roboto Mono'; + font-size: 11px; + line-height: 1.6; + border-bottom: 1px solid #DDD; + background-color: #EEE; + flex: none; + &:after { + content: ''; + display: block; + clear: both; + } +} + + label { display: block; line-height: 1.4em; @@ -160,6 +213,10 @@ input:checked + .toggle-switch { width: 100%; } +div[editor-type="markdown"] .title-input.page-title input[type="text"] { + max-width: 100%; +} + .search-box { max-width: 100%; position: relative; diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index aa7c2f471..e0b1a99cb 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -189,12 +189,13 @@ form.search-box { } } -.setting-nav { +.nav-tabs { text-align: center; - a { + a, .tab-item { padding: $-m; display: inline-block; color: #666; + cursor: pointer; &.selected { border-bottom: 2px solid $primary; } diff --git a/resources/assets/sass/_image-manager.scss b/resources/assets/sass/_image-manager.scss index 8b18d24f3..73b3b59d6 100644 --- a/resources/assets/sass/_image-manager.scss +++ b/resources/assets/sass/_image-manager.scss @@ -120,7 +120,6 @@ .image-manager-list { overflow-y: scroll; flex: 1; - border-top: 1px solid #ddd; } .image-manager-content { @@ -128,6 +127,12 @@ flex-direction: column; height: 100%; flex: 1; + .container { + width: 100%; + } + .full-tab { + text-align: center; + } } // Dropzone diff --git a/resources/assets/sass/_tables.scss b/resources/assets/sass/_tables.scss index 999d65f8d..e6ec76b38 100644 --- a/resources/assets/sass/_tables.scss +++ b/resources/assets/sass/_tables.scss @@ -24,4 +24,13 @@ table { background-color: #F8F8F8; font-weight: 500; } +} + +table.list-table { + margin: 0 -$-xs; + td { + border: 0; + vertical-align: middle; + padding: $-xs; + } } \ No newline at end of file diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index 721eeb238..1a55cf868 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -157,6 +157,12 @@ span.code { @extend .code-base; padding: 1px $-xs; } + +pre code { + background-color: transparent; + border: 0; + font-size: 1em; +} /* * Text colors */ diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 7c7821242..d8453b9ed 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -176,4 +176,29 @@ $btt-size: 40px; position: relative; top: -5px; } +} + +.contained-search-box { + display: flex; + input, button { + border-radius: 0; + border: 1px solid #DDD; + margin-left: -1px; + } + input { + flex: 5; + &:focus, &:active { + outline: 0; + } + } + button { + width: 60px; + } + button i { + padding: 0; + } + button.cancel.active { + background-color: $negative; + color: #EEE; + } } \ No newline at end of file diff --git a/resources/views/form/restriction-checkbox.blade.php b/resources/views/form/restriction-checkbox.blade.php index a4449ccb8..5a8662b56 100644 --- a/resources/views/form/restriction-checkbox.blade.php +++ b/resources/views/form/restriction-checkbox.blade.php @@ -1,7 +1,6 @@ \ No newline at end of file diff --git a/resources/views/pages/form.blade.php b/resources/views/pages/form.blade.php index d8dc19ec2..7ce9dbfe5 100644 --- a/resources/views/pages/form.blade.php +++ b/resources/views/pages/form.blade.php @@ -1,5 +1,5 @@ -
For performance reasons, all images are public by default, This option adds a random, hard-to-guess characters in front of image names. Ensure directory indexes are not enabled to prevent easy access.
-Select which editor will be used by all users to edit pages.
+This image should be 43px in height.
Large images will be scaled down.
This should be a hex value.
Leave empty to reset to the default color.
Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
Note that users will be able to change their email addresses after successful registration.