diff --git a/app/Http/Controllers/AjaxController.php b/app/Http/Controllers/AjaxController.php index 4725f0b..e8a0dd5 100755 --- a/app/Http/Controllers/AjaxController.php +++ b/app/Http/Controllers/AjaxController.php @@ -50,7 +50,7 @@ public function uploadMedia() $fcontents); if($success) { - return response()->json(['Upload successful'], 200); + return response()->json(['Upload successful'], 200); } else { return response()->json(['Failed to move file to directory'], 500); @@ -65,65 +65,20 @@ public function getPageContent($id, $raw = "no") try { $page = Page::findOrFail($id); - $page->content = Markdown::convertToHtml($page->content); - - return response()->json([$page->content, 200]); + return response()->json([$page->html()->content, 200]); } catch (ModelNotFoundException $e) { return response()->json(["Couldn't find page!", 404]); } } else { // "Nice" HTML with variables parsed try { - $page = Page::findOrFail($id); - - $page->content = Markdown::convertToHtml($page->content); + $page = Page::findOrFail($id)->parsed(); } catch (ModelNotFoundException $e) { return response()->json(["Couldn't find page!", 404]); } - // TODO: Export this whole bunch of code - - // BEGIN DISPLAY WIKILINKS: Preg-replace the Wikilinks - $pattern = "/\[\[(.*?)\]\]/i"; - $page->content = preg_replace_callback($pattern, function($matches) { - // Replace the Link text with the title of each found wikilink - - if(strpos($matches[1], '|') > 0) - { - // The second part is what we need - $text = explode('|', $matches[1]); - $matches[1] = $text[0]; - if(strlen($text[1]) == 0) { - $text = $matches[1]; - } - else { - $text = $text[1]; - } - } - else { - // If no linktext was given, just display the match. - $text = $matches[1]; - } - - // Now that we for sure have only the slug in $matches[1], get the page - $page = Page::where('slug', $matches[1])->get()->first(); - - if($page !== null) { - // We got a match -> replace the link - - if($text === $matches[1]) { - $text = $page->title; - } - - return "" . $text . ""; - } - else { - // No page with this name exists -> link to create page - return "$text"; - } - }, $page->content); - // END DISPLAY WIKILINKS + // Prepare possible variables and replace them // Available variables: %wordcount%, %pagecount% @@ -137,23 +92,6 @@ public function getPageContent($id, $raw = "no") return number_format($this->pagecount()); }, $page->content); - // Labels - $page->content = preg_replace_callback("(%label\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%success\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%warning\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%error\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%muted\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - return response()->json([$page->content, 200]); } } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index a100dd6..8798554 100755 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -49,7 +49,7 @@ public function __construct() protected function validator(array $data) { return Validator::make($data, [ - 'name' => 'required|max:255', + 'name' => 'required|max:255|unique:users', 'email' => 'required|email|max:255|unique:users', 'password' => 'required|min:6|confirmed', ]); diff --git a/app/Http/Controllers/Backend.php b/app/Http/Controllers/Backend.php index bc7bade..e0b43be 100644 --- a/app/Http/Controllers/Backend.php +++ b/app/Http/Controllers/Backend.php @@ -9,17 +9,29 @@ use App\Http\Requests; use App\PageIndex; +use App\User; + +use Auth; use Validator; +use Illuminate\Support\Facades\Hash; + class Backend extends Controller { + public function __construct() + { + if(env('AUTH_ACTIVE', true)) { + $this->middleware('auth'); + } + } + /** * Displays an initial "dashboard" * @return View The dashboard view */ public function index() - { + { // dd(session()->all()); $page = new \stdClass(); $stats = new \stdClass(); $page->title = "Dashboard"; @@ -162,16 +174,17 @@ public function backupDatabase() ->header('Content-Disposition', 'attachment; filename="' . env('DB_DATABASE', '') . '"'); } - public function flushViews() + public function flushCache() { - // Run the artisan command + // Run the artisan commands Artisan::call('view:clear'); + Artisan::call('cache:clear'); // And return return redirect('/admin'); } - public function logs($flags = "tail") + public function logs($file = "current", $flags = "tail") { $page = new \stdClass(); $page->title = 'Logs'; @@ -183,34 +196,165 @@ public function logs($flags = "tail") $latest = ""; // Get the logfile with the newest information - foreach($logfiles as $file) { + foreach($logfiles as &$f) { // Exclude any non-log files - if(substr($file, -3) !== "log") { + if(substr($f, -3) !== "log") { continue; } - if(File::lastModified($file) > $lastmod) { - $lastmod = File::lastModified($file); - $latest = $file; + if(File::lastModified($f) > $lastmod) { + $lastmod = File::lastModified($f); + // Replace the name for better readability in the view + $f = File::name($f) . '.' . File::extension($f); + $latest = $f; } } - // Now extract the tail of the latest logfile (if $flags != "all") - if($flags == "tail") { - $logcontent = File::get($latest); - $tail = preg_split("/((\r?\n)|(\r\n?))/", $logcontent); - // Get only the last 40 lines - $firstline = sizeof($tail) - 40; - $tail = array_slice($tail, $firstline); - - for($i = 0; $i < sizeof($tail); $i++) { - $lines[$i] = new \stdClass(); - $lines[$i]->number = $firstline + $i; - $lines[$i]->contents = $tail[$i]; + $theFile = storage_path() . '/logs/' . (($file == 'current') ? $latest : $file); + + $logcontent = File::get($theFile); + $logcontent = preg_split("/((\r?\n)|(\r\n?))/", $logcontent); + + $logBegin = ($flags == 'tail') ? sizeof($logcontent) - 40 : 0; + + $firstline = $logBegin; + $logcontent = array_slice($logcontent, $logBegin); + $lines = []; + + for($i = 0; $i < sizeof($logcontent); $i++) { + $lines[$i] = new \stdClass(); + $lines[$i]->number = $logBegin + $i; + $lines[$i]->contents = $logcontent[$i]; + } + + $theFile = File::name($theFile) . '.' . File::extension($theFile); + + return view('admin.logs', compact('logfiles', 'lines', 'page', 'theFile')); + } + + public function getToken() + { + $page = new \stdClass(); + $page->title = trans('ui.backend.settings.token_title'); + + $tokenfile = storage_path() . '/app/token.txt'; + + if(!File::exists($tokenfile)) { + File::put($tokenfile, ''); + } + + $token = []; + foreach(preg_split("/((\r?\n)|(\r\n?))/", File::get($tokenfile)) as $line) { + if(strpos($line, '=') <= 0) { + continue; } + + $line = explode("=", $line); + + $token[] = ['token' => $line[0], 'uses' => $line[1]]; } - return view('admin.logs', compact('logfiles', 'lines', 'page')); + return view('admin.token', compact('page', 'token')); + } + + public function postToken(Request $request) + { + $tokenfile = storage_path() . '/app/token.txt'; + $token = []; + + if(!File::exists($tokenfile)) { + File::put($tokenfile, ''); + } + + // First lets check whether we should create new token + if($request->has('reg_token') && intval($request->reg_token) > 0) { + $uses = ($request->reg_token_uses > 0) ? $request->reg_token_uses : 1; + + for($i = 0; $i < $request->reg_token; $i++) { + $token[] = ['token' => md5(random_bytes(32)), 'uses' => $uses]; + } + } + + // Now our other token with updated uses + if($request->has('token')) { + for($i = 0; $i < sizeof($request->token); $i++) { //foreach($request->token as $key => $t) { + $token[] = ['token' => $request->token[$i], 'uses' => $request->uses[$i]]; + } + } + + // Unset all token without uses + foreach($token as $key => $t) { + if($t['uses'] <= 0) { + unset($token[$key]); + } + } + + // Now just save the token. + foreach($token as $key => $t) { + $token[$key] = $t['token'] . '=' . $t['uses']; + } + + File::put($tokenfile, implode("\n", $token)); + + return redirect('/admin/token'); + } + + public function getAccount() + { + $page = new \stdClass(); + $page->title = 'Account'; + + if(!Auth::check()) { + return redirect('/admin'); // Return non-logged-in users as they won't see anything here. + } + + return view ('admin.account', compact('page')); + } + + public function postAccount(Request $request) + { + if(!Auth::check()) { + return redirect('/admin'); // Cannot update info for no user + } + + // Check password + if(!Auth::attempt(['email' => Auth::user()->email, 'password' => $request->old_password])) { + return redirect('/admin/account')->withInput()->withErrors(['old_password' => 'Your old password was wrong']); + } + + $validator = Validator::make($request->all(), [ + 'name' => 'required|max:255|unique:users,name,'. Auth::user()->id, + 'email' => 'required|email|max:255|unique:users,email,' . Auth::user()->id, + 'old_password' => 'required|min:6', + 'password' => 'min:6|confirmed', + ]); + + if($validator->fails()) { + return redirect('/admin/account')->withInput()->withErrors($validator); + } + + $user = User::find(Auth::user()->id); + + $user->name = $request->name; + $user->email = $request->email; + + $user->save(); + + return redirect('/admin/account'); + } + + public function regenerateToken() + { + if(!Auth::check()) { + return redirect('/admin'); // Cannot regenerate a token for no user + } + + $user = User::find(Auth::user()->id); + + $user->api_token = Hash::make(random_bytes(32)); // For now only a simple token + $user->save(); + + return redirect('/admin/account'); } /************************************ @@ -253,10 +397,12 @@ public function countIndexedWords() */ public function getCacheSize() { - $files = File::allFiles(storage_path() . '/framework/views'); - $size = 0; - foreach($files as $file) { + foreach(File::allFiles(storage_path() . '/framework/views') as $file) { + $size += File::size($file); + } + + foreach(File::allFiles(storage_path() . '/framework/cache') as $file) { $size += File::size($file); } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 82ecb95..ce6baca 100755 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -11,9 +11,13 @@ use Illuminate\Support\Collection; use App\Page; +use App; + +use Auth; use Validator; -use DB; + +use Illuminate\Support\Facades\Cache; use GrahamCampbell\Markdown\Facades\Markdown; use League\HTMLToMarkdown\HtmlConverter; @@ -41,8 +45,7 @@ public function listPages() $pattern = "/\[\[(.*?)\]\]/i"; foreach($pages as $page) { - $referencingPages = DB::select('SELECT page1_id FROM page_page WHERE page2_id = ' . $page->id . ';'); - if(count($referencingPages) == 0) { + if(count($page->getReferencingPages()) == 0) { $unreferencedPages->push($page); } @@ -87,73 +90,13 @@ public function show($slug, $term = "") $page = Page::where('slug', $slug)->get()->first(); // Count the results if(count($page) != 1) { - // Redirect to creation page - return redirect('/create/'.$slug); + return App::abort(404); } - // Before converting to html we need to assign a little bit nicer quotes to the content - // Currently, only german quotes will be set, but in the future, depending on the language - // we could employ all quotes necessary - // Deactivated (Reason: Some nasty errors in display) - // $page->content = preg_replace("/[\W+][\"](.+?)[\"][\W+]/is", " „$1“ ", $page->content); - - $page->content = Markdown::convertToHtml($page->content); - - if($term !== "") { - // We have a term to highlight, so do it! - // We have to do this BEFORE we replace such stuff like wiki links - // as otherwise we can corrupt our links - - $term = explode(" ", $term); - - for($i = 0; $i < sizeof($term); $i++) { - //$page->content = preg_replace("/[^\[\>](" . $term[$i] . ")/i", ' $1', $page->content); - $page->content = preg_replace("/(" . $term[$i] . ")/i", ' $1', $page->content); - } - } - - // BEGIN DISPLAY WIKILINKS: Preg-replace the Wikilinks - $pattern = "/\[\[(.*?)\]\]/i"; - $page->content = preg_replace_callback($pattern, function($matches) { - // Replace the Link text with the title of each found wikilink - - if(strpos($matches[1], '|') > 0) - { - // The second part is what we need - $text = explode('|', $matches[1]); - $matches[1] = $text[0]; - if(strlen($text[1]) == 0) { - $text = $matches[1]; - } - else { - $text = $text[1]; - } - } - else { - // If no linktext was given, just display the match. - $text = $matches[1]; - } - - // Now that we for sure have only the slug in $matches[1], get the page - $page = Page::where('slug', $matches[1])->get()->first(); - - if($page !== null) { - // We got a match -> replace the link - - if($text === $matches[1]) { - $text = $page->title; - } + // Now parse + $page->highlight($term)->parsed(); - return "" . $text . ""; - } - else { - // No page with this name exists -> link to create page - return "$text"; - } - }, $page->content); - // END DISPLAY WIKILINKS - - // Prepare possible variables and replace them + // Replace variables // Available variables: %wordcount%, %pagecount% $page->content = preg_replace_callback("(%wordcount%)", function($matches) { // Compute the wordcount and replace @@ -165,38 +108,8 @@ public function show($slug, $term = "") return number_format($this->pagecount()); }, $page->content); - // Labels - $page->content = preg_replace_callback("(%label\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%success\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%warning\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%error\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - $page->content = preg_replace_callback("(%muted\|(.+?)%)", function($matches) { - return '' . $matches[1] . ''; - }, $page->content); - - // Now get the referencing pages for this - // In our database, always page2_id is the page - // that is being referenced by page1_id, so we - // need to get page1_id where page2_id is this id - $referencingPages = DB::select('SELECT page1_id FROM page_page WHERE page2_id = ' . $page->id . ';'); - - // Now we have an array with all IDs from the referencing pages, but we - // need title and slug from these pages - $tmp = []; - for($i = 0; $i < count($referencingPages); $i++) - { - $tmp[] = $referencingPages[$i]->page1_id; - } - // whereIn takes an array and selects all (in this case) IDs from the array - $referencingPages = Page::whereIn('id', $tmp)->get(); + // Get the referencing pages + $referencingPages = Page::whereIn('id', $page->getReferencingPages())->get(); return view('page.show', compact('page', 'referencingPages')); } @@ -208,6 +121,10 @@ public function show($slug, $term = "") */ public function getCreate($slug = NULL) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + if(isset($slug)) { return view('page.create', compact('slug')); } @@ -222,6 +139,10 @@ public function getCreate($slug = NULL) */ public function postCreate(Request $request) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + // Create a validator $validator = Validator::make($request->all(), [ 'title' => 'required|max:255', @@ -264,19 +185,28 @@ public function postCreate(Request $request) */ public function getEdit($slug) { - $page = Page::where('slug', $slug)->get()->first(); + if(!env('AUTH_GUEST_EDIT') && !Auth::check()) { + App::abort(401); + } + + $page = Page::where('slug', $slug)->get()->first()->raw(); return view('page.edit', compact('page')); } /** * Inserts edited contents of pages into the database + * * @param Request $request The request object * @param Mixed $flags Potential flags (like "json") or null * @return Redirect Either to the page or on errors to edit page */ public function postEdit(Request $request, $flags = null) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + // Create a validator $validator = Validator::make($request->all(), [ 'title' => 'required|max:255', @@ -284,37 +214,20 @@ public function postEdit(Request $request, $flags = null) ]); if ($validator->fails()) { - if($flags === "json") { - return response()->json([$validator, 400]); // Bad request - } - else { - return redirect('/edit/'.$request->slug) - ->withErrors($validator) - ->withInput(); - } + return redirect('/edit/'.$request->slug) + ->withErrors($validator) + ->withInput(); } $page = Page::where('slug', $request->slug)->get()->first(); if($page === null) { - if($flags === "json") { - return response()->json(['Page was not found', 404]); - } - else { - return redirect('/'); - } + return redirect('/'); } // Everything good? Edit! $page->title = $request->title; $page->content = $request->content; - // If the page has been updated via ContentTools we have to convert to - // markdown (first to remove the editing classes and second to Simply - // save space on the server). - if($flags === "json") { - $converter = new HtmlConverter(); - $page->content = $converter->convert($page->content); - } $page->save(); // Now update all links this page may link to @@ -328,12 +241,48 @@ public function postEdit(Request $request, $flags = null) $indexingRequest = Request::create('/searchEngine/rebuild/' . $page->id, 'GET'); $response = \Route::dispatch($indexingRequest); - if($flags === "json") { - return response()->json([$page->content, 200]); + return redirect('/'.$request->slug); + } + + public function postEditAPI(Request $request) + { + // Create a validator + $validator = Validator::make($request->all(), [ + 'title' => 'required|max:255', + 'content' => 'required|min:10' + ]); + + if ($validator->fails()) { + return response()->json([$validator, 400]); // Bad request } - else { - return redirect('/'.$request->slug); + + $page = Page::where('slug', $request->slug)->get()->first(); + + if($page === null) { + return redirect('/'); } + + // Everything good? Edit! + $page->title = $request->title; + $page->content = $request->content; + // If the page has been updated via ContentTools we have to convert to + // markdown (first to remove the editing classes and second to Simply + // save space on the server). + $converter = new HtmlConverter(); + $page->content = $converter->convert($page->content); + $page->save(); + + // Now update all links this page may link to + $pageCollection = new Collection(); + $pageCollection->push($page); + $this->updateLinks($pageCollection); + + // One last thing to do: Update the search index. Therefore we will "call" + // the rebuilder from within this function via a route we will process + // internally (i.e. as if we would've visit this route) + $indexingRequest = Request::create('/searchEngine/rebuild/' . $page->id, 'GET'); + $response = \Route::dispatch($indexingRequest); + return response()->json([$page->content, 200]); } /** @@ -342,6 +291,10 @@ public function postEdit(Request $request, $flags = null) */ public function showTrash() { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + $pages = Page::onlyTrashed()->get(); // The app-view always requires $page->title variable to be set @@ -358,6 +311,10 @@ public function showTrash() */ public function emptyTrash() { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + $pages = Page::onlyTrashed()->get(); foreach($pages as $page) { @@ -374,6 +331,10 @@ public function emptyTrash() */ public function trash($id) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + try { $page = Page::findOrFail($id); } @@ -398,6 +359,10 @@ public function trash($id) */ public function restoreFromTrash($id) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + $page = Page::withTrashed()->where('id', $id)->get()->first(); if($page === null) { @@ -418,17 +383,21 @@ public function wordcount() // Calculates a wordcount for the whole wiki // Could do it nicer, but seriously, nobody // needs such exact amounts - $count = 0; - $pages = Page::all(); + return Cache::remember('wordcount', 60, function() { + $count = 0; - foreach($pages as $page) - { - $cntwords = explode(' ', $page->content); - $count += count($cntwords); - } + $pages = Page::all(); + + foreach($pages as $page) + { + $cntwords = explode(' ', $page->content); + $count += count($cntwords); + } + + return $count; + }); - return $count; } /** @@ -437,8 +406,9 @@ public function wordcount() */ public function pagecount() { - // Pretty easy: Return number of pages in this wiki - return Page::all()->count(); + return Cache::remember('pagecount', 60, function() { + return Page::all()->count(); + }); } /** @@ -448,6 +418,10 @@ public function pagecount() */ public function updateLinks($pages = null) { + if(!env('AUTH_GUEST_EDIT') || !Auth::check()) { + App::abort(401); + } + // In $pages only should be one page (when called from within postEdit, // which happens on every edit) // But basically it can also hold a distinctive array of pages (to not diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..8eb6be2 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,133 @@ +title = 'Login'; + + return view('user.login', compact('page')); + } + + public function postLogin(Request $request) + { + $success = false; + + // First determine if the user used his name or his email. + if(!filter_var($request->name, FILTER_VALIDATE_EMAIL) === false) { + // We have an email address + $success = Auth::attempt(['email' => $request->name, 'password' => $request->password]); + } else { + // We have a user name + $success = Auth::attempt(['name' => $request->name, 'password' => $request->password]); + } + + if($success) { + return redirect('/'); + } else { + return redirect('/login')->withInput()->withErrors(['auth' => 'We could not find a user with these credentials. Please double check.']); + } + } + + public function getRegistration() + { + $page = new \stdClass(); + $page->title = 'Register'; + + // Show the form + return view('user.register', compact('page')); + } + + public function postRegistration(Request $request) + { + $validator = Validator::make($request->all(), [ + 'name' => 'required|max:255|unique:users', + 'email' => 'required|email|max:255|unique:users', + 'password' => 'required|min:6|confirmed', + ]); + + if($validator->fails()) { + return redirect('/register')->withInput()->withErrors($validator); + } + + if(!env('AUTH_REGISTER')) { + $tokenfile = storage_path() . '/app/token.txt'; + if(!$request->has('register_token')) { + return redirect('/register')->withInput()->withErrors(['register_token' => 'Please provide your unique token to register!']); + } + $tokenfound = false; + // Tokens are saved in a file under storage/app/token.txt + if(!File::exists($tokenfile)) { + return redirect('/register')->withInput()->withErrors(['register_token' => 'There is no token available for registration.']); + } + + $token = []; + foreach(preg_split("/((\r?\n)|(\r\n?))/", File::get($tokenfile)) as $line) { + if(strpos($line, '=') <= 0) { + continue; + } + + $line = explode('=', $line); + + if($line[0] == $request->register_token && intval($line[1]) > 0) { + $tokenfound = true; + // Decrease the uses by one + $token[] = ['token' => $line[0], 'uses' => intval($line[1]) - 1]; + } else { + $token[] = ['token' => $line[0], 'uses' => intval($line[1])]; + } + } + + // Now check expired tokens. + foreach($token as $key => $t) { + if($t['uses'] <= 0) { + unset($token[$key]); + } + } + + // And re-save the file. + foreach($token as $key => $t) { + $token[$key] = $t['token'] . '=' . $t['uses']; + } + File::put($tokenfile, implode("\n", $token)); + + if(!$tokenfound) { + return redirect('/register')->withInput()->withErrors(['register_token' => 'We could not confirm your token.']); + } + } + + // Create a new user + $user = new User(); + $user->name = $request->name; + $user->password = Hash::make($request->password); + $user->email = $request->email; + $user->api_token = Hash::make(random_bytes(32)); // For now only a simple token + $user->save(); + + // Now redirect to the login page + return redirect('/login'); + } + + public function logout() + { + Auth::logout(); + + return redirect('/login'); + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index b747441..0550884 100755 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -40,9 +40,33 @@ Route::post('/settings', 'Backend@saveSettings'); Route::get ('/advancedSettings', 'Backend@advancedSettings'); Route::post('/advancedSettings', 'Backend@saveSettings'); // As only changed settings are saved we can reuse the function - Route::get ('/flushViews', 'Backend@flushViews'); + Route::get ('/flushCache', 'Backend@flushCache'); Route::get ('/backupDatabase', 'Backend@backupDatabase'); - Route::get ('/logs', 'Backend@logs'); + Route::get ('/logs/{file?}/{flags?}', 'Backend@logs'); + Route::get ('/token', 'Backend@getToken'); + Route::post('/token', 'Backend@postToken'); + Route::get ('/account', 'Backend@getAccount'); + Route::post('/account', 'Backend@postAccount'); + Route::get('/regenerate-api-token', 'Backend@regenerateToken'); +}); + + +/****************************************************************************** + * Login and stuff * + ******************************************************************************/ + +Route::get ('login', 'UserController@getLogin'); +Route::post('login', 'UserController@postLogin'); +Route::get ('register', 'UserController@getRegistration'); +Route::post('register', 'UserController@postRegistration'); +Route::get ('logout', 'UserController@logout'); + +/****************************************************************************** + * API specific routes * + ******************************************************************************/ + +Route::group(['prefix' => 'api', 'middleware' => 'auth:api'], function() { + Route::post('edit', 'PageController@postEditAPI'); }); /****************************************************************************** diff --git a/app/Page.php b/app/Page.php index c7840a8..5672ba1 100755 --- a/app/Page.php +++ b/app/Page.php @@ -6,24 +6,258 @@ use Illuminate\Database\Eloquent\SoftDeletes; +use DB; +use GrahamCampbell\Markdown\Facades\Markdown; + +use Illuminate\Support\Facades\Cache; + class Page extends Model { use SoftDeletes; /** - * The attributes that should be mutated to dates. + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; + + /** + * In which state is this Model? Can be raw|html|parsed + * @var string + */ + protected $state = 'raw'; + + /** + * An array with the related-pages-IDs + * + * @var array + */ + protected $referencingPages = []; + + /** + * Have the referencing pages already been retrieved? + * + * @var boolean + */ + protected $referencesRetrieved = false; + + /** + * A prefix for the cache. * - * @var array + * @var string */ - protected $dates = ['deleted_at']; + protected $cachePrefix = 'page_'; + /** + * Define Many-to-many-relationship with other pages + * + * @return Page Return this instance + */ public function pages() { return $this->belongsToMany('App\Page', 'page_page', 'page1_id', 'page2_id')->withTimeStamps(); } + /** + * Define the one-on-one-relationship with the page Indices + * + * @return Page Return this instance + */ public function page_index() { return $this->hasOne('App\PageIndex'); } + + public function save(array $options = []) + { + // Flush the page's cached files so they get updated the next time + Cache::pull($this->cachePrefix . $this->slug . '_html'); + Cache::pull($this->cachePrefix . $this->slug . '_parsed'); + + return parent::save($options); + } + + /** + * Sets the state of this model. Can be 'raw'|'html'|'parsed' + * + * @param string $newState The new state + */ + protected function setState($newState) + { + $this->state = $newState; + + return $this; + } + + /** + * Does not do anything, only for semantics + * + * @return Page This instance + */ + public function raw() + { + return $this; + } + + /** + * Parses the page's content into html + * + * @return page The this-instance + */ + public function html() + { + if(Cache::has($this->cachePrefix . $this->slug . '_html')) { + $this->content = Cache::get($this->cachePrefix . $this->slug . '_html'); + return $this->setState('html'); + } + + $this->content = Markdown::convertToHtml($this->content); + Cache::put($this->cachePrefix . $this->slug . '_html', $this->content, 22*60); // Store for 22 hours + + return $this->setState('html'); + } + + /** + * Parses the page's content so that variables like pagecount etc. can be replaced + * + * @return Page This instance + */ + public function parsed() + { + // First check if this page has been parsed before. Then we don't need to + // bother here. + if(Cache::has($this->cachePrefix . $this->slug . '_parsed')) { + $this->content = Cache::get($this->cachePrefix . $this->slug . '_parsed'); + return $this->setState('parsed'); + } + + // Are we already in a parsed state? Then return. + if($this->state == 'parsed') { + return $this; + } + + // We need parsed HTML to work with the content. + if($this->state == 'raw') { + $this->html(); + } + + // Parse WikiLinks + $pattern = "/\[\[(.*?)\]\]/i"; + $this->content = preg_replace_callback($pattern, function($matches) { + // Replace the Link text with the title of each found wikilink + + if(strpos($matches[1], '|') > 0) + { + // The second part is what we need + $text = explode('|', $matches[1]); + $matches[1] = $text[0]; + if(strlen($text[1]) == 0) { + $text = $matches[1]; + } + else { + $text = $text[1]; + } + } + else { + // If no linktext was given, just display the match. + $text = $matches[1]; + } + + // Now that we for sure have only the slug in $matches[1], get the page + $page = Page::where('slug', $matches[1])->get()->first(); + + if($page !== null) { + // We got a match -> replace the link + + if($text === $matches[1]) { + $text = $page->title; + } + + return "" . $text . ""; + } + else { + // No page with this name exists -> link to create page + return "$text"; + } + }, $this->content); + // END DISPLAY WIKILINKS + + // Labels + $this->content = preg_replace_callback("(%label\|(.+?)%)", function($matches) { + return '' . $matches[1] . ''; + }, $this->content); + $this->content = preg_replace_callback("(%success\|(.+?)%)", function($matches) { + return '' . $matches[1] . ''; + }, $this->content); + $this->content = preg_replace_callback("(%warning\|(.+?)%)", function($matches) { + return '' . $matches[1] . ''; + }, $this->content); + $this->content = preg_replace_callback("(%error\|(.+?)%)", function($matches) { + return '' . $matches[1] . ''; + }, $this->content); + $this->content = preg_replace_callback("(%muted\|(.+?)%)", function($matches) { + return '' . $matches[1] . ''; + }, $this->content); + + // Now save in cache. + Cache::put($this->cachePrefix . $this->slug . '_parsed', $this->content, 22*60); // Store for 22 hours + + // For chainability + return $this->setState('parsed'); + } + + /** + * Highlights an array of terms to highlight + * + * @param string|array $terms A string or an array with terms to highlight + * + * @return Page Return this instance + */ + public function highlight($terms) + { + if($terms == "") { + // Nothing to highlight + return $this; + } + + if(!is_array($terms)) { + $terms = explode(" ", $terms); + } + + foreach($terms as $term) { + $term = trim($term); + $this->content = preg_replace("/(" . $term . ")/i", '$1', $this->content); + } + + return $this; + } + + /** + * Return the pages that link to $this + * + * @return array An array containing all IDs. + */ + public function getReferencingPages() + { + if($this->referencedRetrieved) { + return $this->referencingPages; + } + + // Page1 is always "the" page and Page2 is always the referenced one. + // So in order to get the pages that reference to this page, we need to + // query the inverse of the relation. + $ref = DB::select('SELECT page1_id FROM page_page WHERE page2_id = ' . $this->id . ';'); + + // Enter into an array + $tmp = []; + for($i = 0; $i < count($ref); $i++) + { + $tmp[] = $ref[$i]->page1_id; + } + + $this->referencesRetrieved = true; + + return $this->referencingPages = $tmp; + } } diff --git a/changelog.md b/changelog.md index 9dd97d9..0001884 100644 --- a/changelog.md +++ b/changelog.md @@ -5,3 +5,7 @@ * Fixed a bug in ContentTools causing the Blockquotes and Heading4 not appearing at the correct position in the toolshelf and some issues of blockquotes tool behaving like the paragraph tool. +* Implemented Caching +* Improved log viewer +* Added authentication +* Token-based registration diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 59aa01a..8ca1c0e 100755 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -14,7 +14,7 @@ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); - $table->string('name'); + $table->string('name')->unique(); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); diff --git a/database/migrations/2016_09_11_184803_add_api_token_to_users.php b/database/migrations/2016_09_11_184803_add_api_token_to_users.php new file mode 100644 index 0000000..68a20fb --- /dev/null +++ b/database/migrations/2016_09_11_184803_add_api_token_to_users.php @@ -0,0 +1,30 @@ +string('api_token')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/public/css/app.min.css b/public/css/app.min.css index f4517c5..d199324 100755 --- a/public/css/app.min.css +++ b/public/css/app.min.css @@ -1,4 +1,4 @@ -@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Regular.otf');font-style: normal;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Italic.otf');font-style: italic;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Bold.otf');font-style: normal;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-BoldItalic.otf');font-style: italic;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Bold.woff') format('woff');font-style: normal;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-BoldItalic.woff') format('woff');font-style: italic;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/LatoLatin-Regular.woff') format('woff');font-style: normal;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Light.woff') format('woff');font-style: normal;font-weight: 300;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-LightItalic.woff') format('woff');font-style: italic;font-weight: 300;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Thin.woff') format('woff');font-style: normal;font-weight: 200;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-ThinItalic.woff') format('woff');font-style: italic;font-weight: 200;text-rendering: optimizeLegibility}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMono.ttf') format('ttf');font-style: normal;font-weight: normal}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMoIt.ttf') format('ttf');font-style: italic;font-weight: normal}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMoBd.ttf') format('ttf');font-style: normal;font-weight: bold}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraBI.ttf') format('ttf');font-style: italic;font-weight: bold}*{box-sizing: border-box}::selection{background-color: #333;color: #eee}body{margin: 0px;padding: 0px;font-size: 16px;font-family: Lato, "Helvetica Neue", "Liberation sans", Arial, sans-serif;background-color: white;cursor: default}@media (min-width: 900px){body{margin-top: 70px}}a{cursor: pointer}h1,h2,h3,h4,h5,h6{font-weight: bold;display: block;padding-left: 20px;margin-bottom: 10px;font-weight: 400}h1,h2{border-bottom: 1px solid #ccc}h1{font-size: 40px}h2{font-size: 36px}h3{font-size: 30px}h4{font-size: 24px}h5{font-size: 20px}h6{font-size: 16px}h1 > small,h2 > small,h3 > small,h4 > small,h5 > small,h6 > small{color: #ccc}@media print{h1 > small,h2 > small,h3 > small,h4 > small,h5 > small,h6 > small{color: #000;font-size: small}}a{color: inherit;text-decoration: none;border-bottom: 1px solid #2467db;transition: .2s ease all}a:hover{color: #2467db;transition: .2s ease all}a.anchor{visibility: hidden}table.log{margin: 10px;padding: 0px;border-collapse: collapse;font-family: "Menlo Regular", "OCB", monospace;font-weight: light;font-size: 11.2px}table.log tr{margin: 0px;padding: 0px}table.log td{margin: 0px;padding: 4px}table.log td.line{background-color: #666;color: #eee}table.log td.content{background-color: #999;color: #333}div.container{margin: 0px;margin-bottom: 30px;height: 100%;padding: 10px;position: relative}@media (min-width: 800px){div.container{margin-left: 5%;margin-right: 5%}}@media print{div.container{margin-left: 2%;margin-right: 2%}}@media (min-width: 900px){div.container{margin-left: 5%;margin-right: 5%}}@media (min-width: 1300px){div.container{margin-left: 10%;margin-right: 10%}}@media (min-width: 1500px){div.container{margin-left: 15%;margin-right: 15%}}div.row{margin-bottom: 20px}div.row:before,div.row:after,div.container:before,div.container:after{display: table;content: " "}footer{background-color: #eee;color: #999;border-top: 1px solid #ccc;padding: 5px;clear: both;text-align: center;position: fixed;bottom: 0px;left: 0px;right: 0px}@media print{footer{display: none}}.hidden{display: none !important}.float-right{float: right}.float-left{float: left}.fixed-bottom-right{position: fixed;bottom: 20px;right: 20px}a.button{display: inline-block;padding: 10px;border: 1px solid #bdd1f4;border-radius: 0px;color: #333;background-color: white;font-size: 12.8px}a.button:hover{background-color: white;border-color: #2467db;color: #2467db}a.button.success{border: 1px solid #7ce190}a.button.success:hover{color: #2ec94d;border: 1px solid #2ec94d}a.button.error{border: 1px solid #efacac}a.button.error:hover{color: #de5757;border: 1px solid #de5757}a.button.warning{border: 1px solid #d9d903}a.button.warning:hover{color: #d9d903;border: 1px solid #fcfc2d}a.button.muted,a.button.muted:hover{color: #aaa;border: 1px solid #aaa;cursor: not-allowed}@media print{a.button{display: none}}span.highlight{background-color: yellow}span.label-primary,span.label-warning,span.label-error,span.label-success,span.label-muted{border-radius: 4px;padding: 2px 8px;margin: 0px 8px;font-family: Lato, "Helvetica Neue", "Arial", sans-serif;vertical-align: text-top;color: white;font-size: 12.8px;font-weight: bold}span.label-primary{background-color: #2467db}span.label-warning{background-color: #d9d903}span.label-error{background-color: #de5757}span.label-success{background-color: #2ec94d}span.label-muted{background-color: #aaa}div.alert{text-align: center;padding: 10px;margin: 10px}div.alert.primary{background-color: #7ca4e9;border: 1px solid #2467db;color: #163e83}div.alert.warning{background-color: #fcfc46;border: 1px solid #d9d903;color: #757502}div.alert.error{background-color: #efacac;border: 1px solid #de5757;color: #ad2222}div.alert.success{background-color: #7ce190;border: 1px solid #2ec94d;color: #1b762d}div.alert.muted{background-color: #ddd;border: 1px solid #aaa;color: #777}div.alert ul{text-align: left}div.task-buttons a.button{margin: 20px 5px;border-radius: 0px}div.task-buttons.fixed{position: fixed;top: 50px;transition: .5s all ease}div.task-buttons.fixed .button{-webkit-box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25);-moz-box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25);box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25)}@media print{div.task-buttons{display: none}}a.brand{color: #999;border: none;display: block;line-height: 30px;margin: -5px 10px;text-align: center}a.brand strong{color: #eee;background-color: #666;padding: 3px 8px}a.brand:hover{color: #2467db}#scroll-button{position: fixed;bottom: 50px;right: 20px;border-bottom: none;line-height: 150%;font-size: 200%;color: #999}#scroll-button:hover{border-bottom: none;color: #2467db}@media print{#scroll-button{display: none}}form{margin: 0px;padding: 0px;margin-top: 20px;width: 100%}form input,form select,form textarea,form option{transition: .2s ease all;height: 30px;font-size: 16px;border: 1px solid #333;color: #333;background-color: white;padding: 6px;display: block}form input:focus,form select:focus,form textarea:focus,form option:focus,form input:hover,form select:hover,form textarea:hover,form option:hover{border: 1px solid #2467db;-webkit-box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75);-moz-box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75);box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75)}form input[type="submit"],form input[type="button"]{margin: 6px}form input[type="submit"]:hover,form input[type="button"]:hover{cursor: pointer}form label{display: block;margin: 6px 0px 6px -6px;color: #2467db}form input[readonly],form input[readonly="readonly"],form input[readonly]:focus,form input[readonly="readonly"]:focus{color: #aaa;border: 1px solid #aaa;cursor: not-allowed}form div.input-group{border: 1px solid #999;padding: 10px;margin-top: 30px;padding-top: 20px;position: relative}form div.input-group span.group-title{color: #999;background-color: white;position: absolute;line-height: 20px;top: -10px;left: 20px;padding: 0px 5px}form span[data-display="tooltip"]:before{content: "i";height: 20px;display: inline-block;width: 20px;line-height: 20px;border-radius: 10px;background-color: #7ca4e9;color: white;border: 1px solid #2467db;font-family: Alegreya, "Georgia", "Times New Roman", serif;font-weight: bold;text-align: center;vertical-align: baseline}div.form-row{width: 100%;height: auto;margin-bottom: 10px;padding-top: 10px;padding-bottom: 20px}nav.nav{background-color: #e5e5e5;color: #666;padding: 0px;border-bottom: 1px solid #ccc;width: 100%;z-index: 1000}@media print{nav.nav{display: none}}@media (min-width: 900px){nav.nav{height: 40px;position: fixed;top: 0px;left: 0px;right: 0px;overflow: hidden}}nav.nav ul{margin: 0px;padding: 0px}nav.nav ul li{list-style: none;border-right: 1px solid #ddd;margin: 0px;text-align: center}@media (min-width: 900px){nav.nav ul li{display: -webkit-flex;float: left;margin: 0px}}nav.nav ul li a{display: block;font-weight: 200;transition: .2s ease all;line-height: 40px;padding-left: 10px;padding-right: 10px}nav.nav ul li a:hover{transition: .2s ease all;background-color: #2467db;color: #FFF}nav.nav .nav-search{padding: 0px}nav.nav .nav-search input{background-color: #e5e5e5;color: #999;padding: 10px;margin: 0px;border: none;height: 40px;width: 100%;transition: .4s all ease}@media (min-width: 900px){nav.nav .nav-search input{position: absolute;width: 100%}}nav.nav .nav-search input:hover,nav.nav .nav-search input:focus{margin: 0px;border: none;outline: 0}nav.nav .nav-search input:focus{background-color: #2467db;color: #eee}article p,article ul,article ol,article blockquote{font-size: 22px;font-family: Alegreya, "Georgia", "Times New Roman", serif;padding: 10px;text-align: justify;-webkit-hyphens: auto;-moz-hyphens: auto;-ms-hyphens: auto;hyphens: auto;margin-top: 0px;margin-bottom: 10px}article p:first-line{padding-left: 25px}article p.ref-pages-list{font-family: Lato, "Helvetica Neue", "Arial", sans-serif;font-weight: 200;background-color: #ddd;border-radius: 4px;color: #666;font-size: 16px}article blockquote{border-left: 6px solid #2467db;margin-left: 20px;color: #333}@media print{article blockquote{color: #333;border: none;margin-left: 7%;margin-right: 7%}}article a{margin: -2px;padding-left: 2px;padding-right: 2px;border-bottom: 2px solid #2467db;border-radius: 2px;transition: .2s ease all}article a:hover{background-color: #2467db;color: #eee}article a.broken{margin: -2px;padding-left: 2px;padding-right: 2px;border-bottom: 2px solid #de5757;border-radius: 2px;transition: .2s ease all}article a.broken:hover{background-color: #de5757;color: inherit}article h1,article h2,article h3,article h4,article h5,article h6{color: #335}article h1:before,article h2:before,article h3:before,article h4:before,article h5:before,article h6:before{content: "#";color: #2ec94d;margin-right: 20px;margin-left: -40px}article ul,article ol{margin: 0px;margin-left: 20px;padding: 0px}article ul li,article ol li{line-height: 140%;margin: 4px 0px}article ul ul li,article ol ul li,article ul ol li,article ol ol li{font-style: italic}article ul ul ul li,article ol ul ul li,article ul ol ul li,article ol ol ul li,article ul ul ol li,article ol ul ol li,article ul ol ol li,article ol ol ol li{font-size: 80%;font-style: normal}article table{padding: 5px;width: 100%;margin: 20px 20px}article table tr{padding: 0px;margin: 0px}article table tr td,article table tr th{margin: 0px;padding: 4px;border-bottom: 1px solid #ddd;font-size: 20px}article hr{border: none;border-bottom: 1px solid #999;width: 90%;margin-top: 10px;margin-bottom: 20px}@media print{article p,article ul,article ol,article blockquote{font-size: 16px;line-height: 110%;margin-bottom: 5px;padding: 5px}article h1,article h2,article h3,article h4,article h5,article h6{font-size: 19.2px}article blockquote{margin: 2%}}div.list-element,a.list-element{padding: 10px 5px;background-color: #eee;border-bottom: 1px solid #ddd;display: block}div.list-element .list-element-header,a.list-element .list-element-header{font-size: 24px;margin: 0px;padding: 0px}div.list-element .list-element-content,a.list-element .list-element-content{padding: 10px}div.list-element:hover,a.list-element:hover{color: inherit;background-color: #d3e1f8}div.toc-parent{perspective: 1000px;position: fixed;top: 40px;left: 0px;width: 100%;bottom: 30px}div.toc-parent.back{transition: 1s all ease;z-index: -1}@media (min-width: 600px){div.toc-parent{width: 15%;min-width: 300px}}.extendfull,.extendleft{padding-left: 3000px;margin-left: -3000px}.extendfull,.extendright{padding-right: 3000px;margin-right: -3000px}div.tooltip{position: absolute;background-color: rgba(25,25,25,0.8);padding: 4px;border-radius: 4px;border-top-left-radius: 2px;border-bottom-left-radius: 2px;color: white;font-weight: 200}div.tooltip:before{content: '';display: block;width: 0;height: 0;position: absolute;border-top: 8px solid transparent;border-bottom: 8px solid transparent;border-right: 10px solid rgba(25,25,25,0.8);left: -10px;top: 6px}div.toc{background-color: rgba(25,25,25,0.8);color: white;margin: 0px;padding: 0px;overflow-y: auto;overflow-x: visible;height: 100%;transform: rotateY(120deg);transition: .8s all ease;transform-origin: 0%}@media (min-width: 600px){div.toc{width: 15%;min-width: 300px}}div.toc.open{transform: rotateY(0deg);transition: .8s all ease}div.toc ul{margin: 0px;padding: 0px}div.toc ul li{list-style: none}div.toc ul li a{border-bottom: 1px solid #ddd;display: block;height: 20px;line-height: 20px;overflow: hidden;padding-left: 5px;transition: .1s all linear;font-weight: normal}div.toc ul li a:hover{transition: .1s all linear;color: #fff;background-color: #2467db}div.toc ul li.class-h1{padding-left: 0px}div.toc ul li.class-h2{padding-left: 5px}div.toc ul li.class-h3{padding-left: 10px}div.toc ul li.class-h4{padding-left: 15px}div.toc ul li.class-h5{padding-left: 20px}div.toc ul li.class-h6{padding-left: 25px}div.toc ul:first-child{margin-top: 20px}div.toc ul:last-child{margin-bottom: 40px}.modal{z-index: 9999;position: fixed;top: 100px;bottom: 100px;left: 100px;right: 100px;background-color: white;border-radius: 6px;padding: 20px;padding-bottom: 60px}.modal .tab{height: 100%}.modal .loading-spinner{width: 60px;height: 60px;margin: 10% 50%}.modal .button-bar{position: absolute;bottom: 0;left: 0;right: 0;height: 60px;border-top: 1px solid #999;padding: 10px}.modal .button-bar *{float: right}.dim{position: fixed;top: 0;bottom: 0;right: 0;left: 0}.dim::after{top: 0;left: 0;width: 100%;height: 100%;content: " ";background-color: rgba(0,0,0,0.5);position: fixed;z-index: 9998}a.lines-button{padding: 5px 5px;transition: .3s;cursor: pointer;user-select: none;border-radius: 2px;background-color: transparent;border: none;margin-top: -10px;float: right;background-color: #e5e5e5;opacity: 0.6;z-index: 1005}a.lines-button:hover{opacity: 1;background-color: #e5e5e5 !important}a.lines-button:active{transition: 0}a.lines-button:focus{outline: 0}a.lines-button > .lines{display: inline-block;width: 30px;height: 3px;background-color: #666;transition: 0.3s;position: relative}a.lines-button > .lines:before,a.lines-button > .lines:after{display: inline-block;width: 30px;height: 3px;background-color: #666;transition: 0.3s;position: absolute;left: 0;content: ''}a.lines-button > .lines:before{top: 10px}a.lines-button > .lines:after{top: -10px}a.lines-button.open .lines{background: transparent}a.lines-button.open .lines:before,a.lines-button.open .linesafter{top: 0;width: 30px}a.lines-button.open .lines:before{-webkit-transform: rotate3d(0, 0, 1, 45deg);transform: rotate3d(0, 0, 1, 45deg);-webkit-transform-origin: 50% 50%;transform-origin: 50% 50%;top: -50}a.lines-button.open .lines:after{-webkit-transform: rotate3d(0, 0, 1, -45deg);transform: rotate3d(0, 0, 1, -45deg);-webkit-transform-origin: 50% 50%;transform-origin: 50% 50%;top: 0}@media (min-width: 900px){a.lines-button{display: none}}#media-library .media-library-file{margin: 20px;padding: 20px;border: 1px solid #999;background-color: #ccc}#media-library #media-library-file-list{overflow: auto;height: 400px}div.tab-nav{display: table;table-layout: fixed;width: 100%}div.tab-nav ul{display: table-row;margin: 0px;padding: 0px}div.tab-nav ul li{display: table-cell;list-style: none;text-align: center;margin: 0px;padding: 0px;background-color: #005C94;transition: .2s all ease}div.tab-nav ul li a{color: white;display: block;text-decoration: none;border: none;border-bottom: 1px solid #2467db;line-height: 300%}div.tab-nav ul li a:focus{outline: 0}div.tab-nav ul li:hover,div.tab-nav ul li[aria-selected="true"]{background-color: #2467db;transition: .2s all ease}div#dropzone{min-height: 200px;height: 75%;overflow: scroll;padding: 10px;margin-top: 20px}div#dropzone:not(.dz-started){border: 10px solid #999;cursor: pointer;position: relative}div#dropzone:not(.dz-started):before{content: "Drop files here or click to upload";text-align: center;color: #999;position: absolute;top: 0;bottom: 0;right: 0;left: 0;display: block;padding: 20px}div.dz-preview{border: 1px solid #aaa;margin: 5px;padding: 2px;clear: both}div.dz-preview .dz-image{display: inline-block;float: left}div.dz-preview .dz-image img{margin: 5px;-webkit-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75);-moz-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75);box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75)}div.dz-preview a.dz-remove{display: inline-block}div.dz-preview .dz-details{display: none}div.dz-preview .dz-img-info{width: 80%}div.dz-preview .dz-success-mark,div.dz-preview .dz-error-mark{display: none}.CodeMirror{height: 100%;width: 100%;margin: 6px;-webkit-box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75);-moz-box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75);box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75)}.CodeMirror pre{font-family: 'Bitstream', Menlo, monospace}.CodeMirror-scroll{height: 500px;cursor: text}.CodeMirror .cm-gfm-wiki{color: #2467db;background-color: #eee}.CodeMirror .CodeMirror-cursor{border-left: 2px solid #2467db}[data-editable] iframe,[data-editable] image,[data-editable] [data-ce-tag=img],[data-editable] img,[data-editable] video{clear: both;display: block;margin-left: auto;margin-right: auto;max-width: 100%}[data-editable] .align-left{clear: initial;float: left;margin-right: 0.5em}[data-editable] .align-right{clear: initial;float: right;margin-left: 0.5em}[data-editable] .text-center{text-align: center}[data-editable] .text-left{text-align: left}[data-editable] .text-right{text-align: right}/*! +@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Regular.otf');font-style: normal;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Italic.otf');font-style: italic;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-Bold.otf');font-style: normal;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: Alegreya;src: url('../fonts/serif/Alegreya-BoldItalic.otf');font-style: italic;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Bold.woff') format('woff');font-style: normal;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-BoldItalic.woff') format('woff');font-style: italic;font-weight: bold;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/LatoLatin-Regular.woff') format('woff');font-style: normal;font-weight: normal;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Light.woff') format('woff');font-style: normal;font-weight: 300;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-LightItalic.woff') format('woff');font-style: italic;font-weight: 300;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-Thin.woff') format('woff');font-style: normal;font-weight: 200;text-rendering: optimizeLegibility}@font-face{font-family: 'Lato';src: url('../fonts/sans/Lato-ThinItalic.woff') format('woff');font-style: italic;font-weight: 200;text-rendering: optimizeLegibility}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMono.ttf') format('ttf');font-style: normal;font-weight: normal}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMoIt.ttf') format('ttf');font-style: italic;font-weight: normal}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraMoBd.ttf') format('ttf');font-style: normal;font-weight: bold}@font-face{font-family: 'Bitstream';src: url('../fonts/mono/VeraBI.ttf') format('ttf');font-style: italic;font-weight: bold}*{box-sizing: border-box}::selection{background-color: #333;color: #eee}body{margin: 0px;padding: 0px;font-size: 16px;font-family: Lato, "Helvetica Neue", "Liberation sans", Arial, sans-serif;background-color: white;cursor: default}@media (min-width: 900px){body{margin-top: 70px}}a{cursor: pointer}h1,h2,h3,h4,h5,h6{font-weight: bold;display: block;padding-left: 20px;margin-bottom: 10px;font-weight: 400}h1,h2{border-bottom: 1px solid #ccc}h1{font-size: 40px}h2{font-size: 36px}h3{font-size: 30px}h4{font-size: 24px}h5{font-size: 20px}h6{font-size: 16px}h1 > small,h2 > small,h3 > small,h4 > small,h5 > small,h6 > small{color: #ccc}@media print{h1 > small,h2 > small,h3 > small,h4 > small,h5 > small,h6 > small{color: #000;font-size: small}}a{color: inherit;text-decoration: none;border-bottom: 1px solid #2467db;transition: .2s ease all}a:hover{color: #2467db;transition: .2s ease all}a.anchor{visibility: hidden}table.log{margin: 10px;padding: 0px;border-collapse: collapse;font-family: "Menlo Regular", "OCB", monospace;font-weight: 200;font-size: 11.2px}table.log tr{margin: 0px;padding: 0px}table.log td{margin: 0px;padding: 4px}table.log td.line{background-color: black;color: #eee;vertical-align: top}table.log td.content{background-color: #666;color: #eee}div.container{margin: 0px;margin-bottom: 30px;height: 100%;padding: 10px;position: relative}@media (min-width: 800px){div.container{margin-left: 5%;margin-right: 5%}}@media print{div.container{margin-left: 2%;margin-right: 2%}}@media (min-width: 900px){div.container{margin-left: 5%;margin-right: 5%}}@media (min-width: 1300px){div.container{margin-left: 10%;margin-right: 10%}}@media (min-width: 1500px){div.container{margin-left: 15%;margin-right: 15%}}div.row{margin-bottom: 20px}div.row:before,div.row:after,div.container:before,div.container:after{display: table;content: " "}footer{background-color: #eee;color: #999;border-top: 1px solid #ccc;padding: 5px;clear: both;text-align: center;position: fixed;bottom: 0px;left: 0px;right: 0px}@media print{footer{display: none}}.hidden{display: none !important}.float-right{float: right}.float-left{float: left}.fixed-bottom-right{position: fixed;bottom: 20px;right: 20px}a.button{display: inline-block;padding: 10px;border: 1px solid #bdd1f4;border-radius: 0px;color: #333;background-color: white;font-size: 12.8px}a.button:hover{background-color: white;border-color: #2467db;color: #2467db}a.button.success{border: 1px solid #7ce190}a.button.success:hover{color: #2ec94d;border: 1px solid #2ec94d}a.button.error{border: 1px solid #efacac}a.button.error:hover{color: #de5757;border: 1px solid #de5757}a.button.warning{border: 1px solid #d9d903}a.button.warning:hover{color: #d9d903;border: 1px solid #fcfc2d}a.button.muted,a.button.muted:hover{color: #aaa;border: 1px solid #aaa;cursor: not-allowed}@media print{a.button{display: none}}span.highlight{background-color: yellow}span.label-primary,span.label-warning,span.label-error,span.label-success,span.label-muted{border-radius: 4px;padding: 2px 8px;margin: 0px 8px;font-family: Lato, "Helvetica Neue", "Arial", sans-serif;vertical-align: text-top;color: white;font-size: 12.8px;font-weight: bold}span.label-primary{background-color: #2467db}span.label-warning{background-color: #d9d903}span.label-error{background-color: #de5757}span.label-success{background-color: #2ec94d}span.label-muted{background-color: #aaa}div.alert{text-align: center;padding: 10px;margin: 10px}div.alert.primary{background-color: #7ca4e9;border: 1px solid #2467db;color: #163e83}div.alert.warning{background-color: #fcfc46;border: 1px solid #d9d903;color: #757502}div.alert.error{background-color: #efacac;border: 1px solid #de5757;color: #ad2222}div.alert.success{background-color: #7ce190;border: 1px solid #2ec94d;color: #1b762d}div.alert.muted{background-color: #ddd;border: 1px solid #aaa;color: #777}div.alert ul{text-align: left}div.task-buttons a.button{margin: 20px 5px;border-radius: 0px}div.task-buttons.fixed{position: fixed;top: 50px;transition: .5s all ease}div.task-buttons.fixed .button{-webkit-box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25);-moz-box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25);box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.25)}@media print{div.task-buttons{display: none}}a.brand{color: #999;border: none;display: block;line-height: 30px;margin: -5px 10px;text-align: center}a.brand strong{color: #eee;background-color: #666;padding: 3px 8px}a.brand:hover{color: #2467db}#scroll-button{position: fixed;bottom: 50px;right: 20px;border-bottom: none;line-height: 150%;font-size: 200%;color: #999}#scroll-button:hover{border-bottom: none;color: #2467db}@media print{#scroll-button{display: none}}form{margin: 0px;padding: 0px;margin-top: 20px;width: 100%}form input,form select,form textarea,form option{transition: .2s ease all;height: 30px;font-size: 16px;border: 1px solid #333;color: #333;background-color: white;padding: 6px;display: block}form input:focus,form select:focus,form textarea:focus,form option:focus,form input:hover,form select:hover,form textarea:hover,form option:hover{border: 1px solid #2467db;-webkit-box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75);-moz-box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75);box-shadow: outset 0px 0px 2px 0px rgba(0,168,224,0.75)}form input[type="submit"],form input[type="button"]{margin: 6px}form input[type="submit"]:hover,form input[type="button"]:hover{cursor: pointer}form label{display: block;margin: 6px 0px 6px -6px;color: #2467db}form input[readonly],form input[readonly="readonly"],form input[readonly]:focus,form input[readonly="readonly"]:focus{color: #aaa;border: 1px solid #aaa;cursor: not-allowed}form div.input-group{border: 1px solid #999;padding: 10px;margin-top: 30px;padding-top: 20px;position: relative}form div.input-group span.group-title{color: #999;background-color: white;position: absolute;line-height: 20px;top: -10px;left: 20px;padding: 0px 5px}form span[data-display="tooltip"]:before{content: "i";height: 20px;display: inline-block;width: 20px;line-height: 20px;border-radius: 10px;background-color: #7ca4e9;color: white;border: 1px solid #2467db;font-family: Alegreya, "Georgia", "Times New Roman", serif;font-weight: bold;text-align: center;vertical-align: baseline}div.form-row{width: 100%;height: auto;margin-bottom: 10px;padding-top: 10px;padding-bottom: 20px}nav.nav{background-color: #e5e5e5;color: #666;padding: 0px;border-bottom: 1px solid #ccc;width: 100%;z-index: 1000}@media print{nav.nav{display: none}}@media (min-width: 900px){nav.nav{height: 40px;position: fixed;top: 0px;left: 0px;right: 0px;overflow: hidden}}nav.nav ul{margin: 0px;padding: 0px}nav.nav ul li{list-style: none;border-right: 1px solid #ddd;margin: 0px;text-align: center}@media (min-width: 900px){nav.nav ul li{display: -webkit-flex;float: left;margin: 0px}}nav.nav ul li a{display: block;font-weight: 200;transition: .2s ease all;line-height: 40px;padding-left: 10px;padding-right: 10px}nav.nav ul li a:hover{transition: .2s ease all;background-color: #2467db;color: #FFF}nav.nav .nav-search{padding: 0px}nav.nav .nav-search input{background-color: #e5e5e5;color: #999;padding: 10px;margin: 0px;border: none;height: 40px;width: 100%;transition: .4s all ease}@media (min-width: 900px){nav.nav .nav-search input{position: absolute;width: 100%}}nav.nav .nav-search input:hover,nav.nav .nav-search input:focus{margin: 0px;border: none;outline: 0}nav.nav .nav-search input:focus{background-color: #2467db;color: #eee}article p,article ul,article ol,article blockquote{font-size: 22px;font-family: Alegreya, "Georgia", "Times New Roman", serif;padding: 10px;text-align: justify;-webkit-hyphens: auto;-moz-hyphens: auto;-ms-hyphens: auto;hyphens: auto;margin-top: 0px;margin-bottom: 10px}article p:first-line{padding-left: 25px}article p.ref-pages-list{font-family: Lato, "Helvetica Neue", "Arial", sans-serif;font-weight: 200;background-color: #ddd;border-radius: 4px;color: #666;font-size: 16px}article blockquote{border-left: 6px solid #2467db;margin-left: 20px;color: #333}@media print{article blockquote{color: #333;border: none;margin-left: 7%;margin-right: 7%}}article a{margin: -2px;padding-left: 2px;padding-right: 2px;border-bottom: 2px solid #2467db;border-radius: 2px;transition: .2s ease all}article a:hover{background-color: #2467db;color: #eee}article a.broken{margin: -2px;padding-left: 2px;padding-right: 2px;border-bottom: 2px solid #de5757;border-radius: 2px;transition: .2s ease all}article a.broken:hover{background-color: #de5757;color: inherit}article h1,article h2,article h3,article h4,article h5,article h6{color: #335}article h1:before,article h2:before,article h3:before,article h4:before,article h5:before,article h6:before{content: "#";color: #2ec94d;margin-right: 20px;margin-left: -40px}article ul,article ol{margin: 0px;margin-left: 20px;padding: 0px}article ul li,article ol li{line-height: 140%;margin: 4px 0px}article ul ul li,article ol ul li,article ul ol li,article ol ol li{font-style: italic}article ul ul ul li,article ol ul ul li,article ul ol ul li,article ol ol ul li,article ul ul ol li,article ol ul ol li,article ul ol ol li,article ol ol ol li{font-size: 80%;font-style: normal}article table{padding: 5px;width: 100%;margin: 20px 20px}article table tr{padding: 0px;margin: 0px}article table tr td,article table tr th{margin: 0px;padding: 4px;border-bottom: 1px solid #ddd;font-size: 20px}article hr{border: none;border-bottom: 1px solid #999;width: 90%;margin-top: 10px;margin-bottom: 20px}@media print{article p,article ul,article ol,article blockquote{font-size: 16px;line-height: 110%;margin-bottom: 5px;padding: 5px}article h1,article h2,article h3,article h4,article h5,article h6{font-size: 19.2px}article blockquote{margin: 2%}}div.list-element,a.list-element{padding: 10px 5px;background-color: #eee;border-bottom: 1px solid #ddd;display: block}div.list-element .list-element-header,a.list-element .list-element-header{font-size: 24px;margin: 0px;padding: 0px}div.list-element .list-element-content,a.list-element .list-element-content{padding: 10px}div.list-element:hover,a.list-element:hover{color: inherit;background-color: #d3e1f8}div.toc-parent{perspective: 1000px;position: fixed;top: 40px;left: 0px;width: 100%;bottom: 30px}div.toc-parent.back{transition: 1s all ease;z-index: -1}@media (min-width: 600px){div.toc-parent{width: 15%;min-width: 300px}}.extendfull,.extendleft{padding-left: 3000px;margin-left: -3000px}.extendfull,.extendright{padding-right: 3000px;margin-right: -3000px}div.tooltip{position: absolute;background-color: rgba(25,25,25,0.8);padding: 4px;border-radius: 4px;border-top-left-radius: 2px;border-bottom-left-radius: 2px;color: white;font-weight: 200}div.tooltip:before{content: '';display: block;width: 0;height: 0;position: absolute;border-top: 8px solid transparent;border-bottom: 8px solid transparent;border-right: 10px solid rgba(25,25,25,0.8);left: -10px;top: 6px}div.toc{background-color: rgba(25,25,25,0.8);color: white;margin: 0px;padding: 0px;overflow-y: auto;overflow-x: visible;height: 100%;transform: rotateY(120deg);transition: .8s all ease;transform-origin: 0%}@media (min-width: 600px){div.toc{width: 15%;min-width: 300px}}div.toc.open{transform: rotateY(0deg);transition: .8s all ease}div.toc ul{margin: 0px;padding: 0px}div.toc ul li{list-style: none}div.toc ul li a{border-bottom: 1px solid #ddd;display: block;height: 20px;line-height: 20px;overflow: hidden;padding-left: 5px;transition: .1s all linear;font-weight: normal}div.toc ul li a:hover{transition: .1s all linear;color: #fff;background-color: #2467db}div.toc ul li.class-h1{padding-left: 0px}div.toc ul li.class-h2{padding-left: 5px}div.toc ul li.class-h3{padding-left: 10px}div.toc ul li.class-h4{padding-left: 15px}div.toc ul li.class-h5{padding-left: 20px}div.toc ul li.class-h6{padding-left: 25px}div.toc ul:first-child{margin-top: 20px}div.toc ul:last-child{margin-bottom: 40px}.modal{z-index: 9999;position: fixed;top: 100px;bottom: 100px;left: 100px;right: 100px;background-color: white;border-radius: 6px;padding: 20px;padding-bottom: 60px}.modal .tab{height: 100%}.modal .loading-spinner{width: 60px;height: 60px;margin: 10% 50%}.modal .button-bar{position: absolute;bottom: 0;left: 0;right: 0;height: 60px;border-top: 1px solid #999;padding: 10px}.modal .button-bar *{float: right}.dim{position: fixed;top: 0;bottom: 0;right: 0;left: 0}.dim::after{top: 0;left: 0;width: 100%;height: 100%;content: " ";background-color: rgba(0,0,0,0.5);position: fixed;z-index: 9998}a.lines-button{padding: 5px 5px;transition: .3s;cursor: pointer;user-select: none;border-radius: 2px;background-color: transparent;border: none;margin-top: -10px;float: right;background-color: #e5e5e5;opacity: 0.6;z-index: 1005}a.lines-button:hover{opacity: 1;background-color: #e5e5e5 !important}a.lines-button:active{transition: 0}a.lines-button:focus{outline: 0}a.lines-button > .lines{display: inline-block;width: 30px;height: 3px;background-color: #666;transition: 0.3s;position: relative}a.lines-button > .lines:before,a.lines-button > .lines:after{display: inline-block;width: 30px;height: 3px;background-color: #666;transition: 0.3s;position: absolute;left: 0;content: ''}a.lines-button > .lines:before{top: 10px}a.lines-button > .lines:after{top: -10px}a.lines-button.open .lines{background: transparent}a.lines-button.open .lines:before,a.lines-button.open .linesafter{top: 0;width: 30px}a.lines-button.open .lines:before{-webkit-transform: rotate3d(0, 0, 1, 45deg);transform: rotate3d(0, 0, 1, 45deg);-webkit-transform-origin: 50% 50%;transform-origin: 50% 50%;top: -50}a.lines-button.open .lines:after{-webkit-transform: rotate3d(0, 0, 1, -45deg);transform: rotate3d(0, 0, 1, -45deg);-webkit-transform-origin: 50% 50%;transform-origin: 50% 50%;top: 0}@media (min-width: 900px){a.lines-button{display: none}}#media-library .media-library-file{margin: 20px;padding: 20px;border: 1px solid #999;background-color: #ccc}#media-library #media-library-file-list{overflow: auto;height: 400px}div.tab-nav{display: table;table-layout: fixed;width: 100%}div.tab-nav ul{display: table-row;margin: 0px;padding: 0px}div.tab-nav ul li{display: table-cell;list-style: none;text-align: center;margin: 0px;padding: 0px;background-color: #005C94;transition: .2s all ease}div.tab-nav ul li a{color: white;display: block;text-decoration: none;border: none;border-bottom: 1px solid #2467db;line-height: 300%}div.tab-nav ul li a:focus{outline: 0}div.tab-nav ul li:hover,div.tab-nav ul li[aria-selected="true"]{background-color: #2467db;transition: .2s all ease}div#dropzone{min-height: 200px;height: 75%;overflow: scroll;padding: 10px;margin-top: 20px}div#dropzone:not(.dz-started){border: 10px solid #999;cursor: pointer;position: relative}div#dropzone:not(.dz-started):before{content: "Drop files here or click to upload";text-align: center;color: #999;position: absolute;top: 0;bottom: 0;right: 0;left: 0;display: block;padding: 20px}div.dz-preview{border: 1px solid #aaa;margin: 5px;padding: 2px;clear: both}div.dz-preview .dz-image{display: inline-block;float: left}div.dz-preview .dz-image img{margin: 5px;-webkit-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75);-moz-box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75);box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75)}div.dz-preview a.dz-remove{display: inline-block}div.dz-preview .dz-details{display: none}div.dz-preview .dz-img-info{width: 80%}div.dz-preview .dz-success-mark,div.dz-preview .dz-error-mark{display: none}.CodeMirror{height: 100%;width: 100%;margin: 6px;-webkit-box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75);-moz-box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75);box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,0.75)}.CodeMirror pre{font-family: 'Bitstream', Menlo, monospace}.CodeMirror-scroll{height: 500px;cursor: text}.CodeMirror .cm-gfm-wiki{color: #2467db;background-color: #eee}.CodeMirror .CodeMirror-cursor{border-left: 2px solid #2467db}[data-editable] iframe,[data-editable] image,[data-editable] [data-ce-tag=img],[data-editable] img,[data-editable] video{clear: both;display: block;margin-left: auto;margin-right: auto;max-width: 100%}[data-editable] .align-left{clear: initial;float: left;margin-right: 0.5em}[data-editable] .align-right{clear: initial;float: right;margin-left: 0.5em}[data-editable] .text-center{text-align: center}[data-editable] .text-left{text-align: left}[data-editable] .text-right{text-align: right}/*! * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family: 'FontAwesome';src: url('../fonts/fontawesome-webfont.eot?v=4.6.3');src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight: normal;font-style: normal}.fa{display: inline-block;font: normal normal normal 14px/1 FontAwesome;font-size: inherit;text-rendering: auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale}.fa-lg{font-size: 1.33333333em;line-height: .75em;vertical-align: -15%}.fa-2x{font-size: 2em}.fa-3x{font-size: 3em}.fa-4x{font-size: 4em}.fa-5x{font-size: 5em}.fa-fw{width: 1.28571429em;text-align: center}.fa-ul{padding-left: 0;margin-left: 2.14285714em;list-style-type: none}.fa-ul > li{position: relative}.fa-li{position: absolute;left: -2.14285714em;width: 2.14285714em;top: .14285714em;text-align: center}.fa-li.fa-lg{left: -1.85714286em}.fa-border{padding: .2em .25em .15em;border: solid .08em #eee;border-radius: .1em}.fa-pull-left{float: left}.fa-pull-right{float: right}.fa.fa-pull-left{margin-right: .3em}.fa.fa-pull-right{margin-left: .3em}.pull-right{float: right}.pull-left{float: left}.fa.pull-left{margin-right: .3em}.fa.pull-right{margin-left: .3em}.fa-spin{-webkit-animation: fa-spin 2s infinite linear;animation: fa-spin 2s infinite linear}.fa-pulse{-webkit-animation: fa-spin 1s infinite steps(8);animation: fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform: rotate(0deg);transform: rotate(0deg)}100%{-webkit-transform: rotate(359deg);transform: rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform: rotate(0deg);transform: rotate(0deg)}100%{-webkit-transform: rotate(359deg);transform: rotate(359deg)}}.fa-rotate-90{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform: rotate(90deg);-ms-transform: rotate(90deg);transform: rotate(90deg)}.fa-rotate-180{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform: rotate(180deg);-ms-transform: rotate(180deg);transform: rotate(180deg)}.fa-rotate-270{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform: rotate(270deg);-ms-transform: rotate(270deg);transform: rotate(270deg)}.fa-flip-horizontal{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform: scale(-1, 1);-ms-transform: scale(-1, 1);transform: scale(-1, 1)}.fa-flip-vertical{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform: scale(1, -1);-ms-transform: scale(1, -1);transform: scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter: none}.fa-stack{position: relative;display: inline-block;width: 2em;height: 2em;line-height: 2em;vertical-align: middle}.fa-stack-1x,.fa-stack-2x{position: absolute;left: 0;width: 100%;text-align: center}.fa-stack-1x{line-height: inherit}.fa-stack-2x{font-size: 2em}.fa-inverse{color: #fff}.fa-glass:before{content: "\f000"}.fa-music:before{content: "\f001"}.fa-search:before{content: "\f002"}.fa-envelope-o:before{content: "\f003"}.fa-heart:before{content: "\f004"}.fa-star:before{content: "\f005"}.fa-star-o:before{content: "\f006"}.fa-user:before{content: "\f007"}.fa-film:before{content: "\f008"}.fa-th-large:before{content: "\f009"}.fa-th:before{content: "\f00a"}.fa-th-list:before{content: "\f00b"}.fa-check:before{content: "\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content: "\f00d"}.fa-search-plus:before{content: "\f00e"}.fa-search-minus:before{content: "\f010"}.fa-power-off:before{content: "\f011"}.fa-signal:before{content: "\f012"}.fa-gear:before,.fa-cog:before{content: "\f013"}.fa-trash-o:before{content: "\f014"}.fa-home:before{content: "\f015"}.fa-file-o:before{content: "\f016"}.fa-clock-o:before{content: "\f017"}.fa-road:before{content: "\f018"}.fa-download:before{content: "\f019"}.fa-arrow-circle-o-down:before{content: "\f01a"}.fa-arrow-circle-o-up:before{content: "\f01b"}.fa-inbox:before{content: "\f01c"}.fa-play-circle-o:before{content: "\f01d"}.fa-rotate-right:before,.fa-repeat:before{content: "\f01e"}.fa-refresh:before{content: "\f021"}.fa-list-alt:before{content: "\f022"}.fa-lock:before{content: "\f023"}.fa-flag:before{content: "\f024"}.fa-headphones:before{content: "\f025"}.fa-volume-off:before{content: "\f026"}.fa-volume-down:before{content: "\f027"}.fa-volume-up:before{content: "\f028"}.fa-qrcode:before{content: "\f029"}.fa-barcode:before{content: "\f02a"}.fa-tag:before{content: "\f02b"}.fa-tags:before{content: "\f02c"}.fa-book:before{content: "\f02d"}.fa-bookmark:before{content: "\f02e"}.fa-print:before{content: "\f02f"}.fa-camera:before{content: "\f030"}.fa-font:before{content: "\f031"}.fa-bold:before{content: "\f032"}.fa-italic:before{content: "\f033"}.fa-text-height:before{content: "\f034"}.fa-text-width:before{content: "\f035"}.fa-align-left:before{content: "\f036"}.fa-align-center:before{content: "\f037"}.fa-align-right:before{content: "\f038"}.fa-align-justify:before{content: "\f039"}.fa-list:before{content: "\f03a"}.fa-dedent:before,.fa-outdent:before{content: "\f03b"}.fa-indent:before{content: "\f03c"}.fa-video-camera:before{content: "\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content: "\f03e"}.fa-pencil:before{content: "\f040"}.fa-map-marker:before{content: "\f041"}.fa-adjust:before{content: "\f042"}.fa-tint:before{content: "\f043"}.fa-edit:before,.fa-pencil-square-o:before{content: "\f044"}.fa-share-square-o:before{content: "\f045"}.fa-check-square-o:before{content: "\f046"}.fa-arrows:before{content: "\f047"}.fa-step-backward:before{content: "\f048"}.fa-fast-backward:before{content: "\f049"}.fa-backward:before{content: "\f04a"}.fa-play:before{content: "\f04b"}.fa-pause:before{content: "\f04c"}.fa-stop:before{content: "\f04d"}.fa-forward:before{content: "\f04e"}.fa-fast-forward:before{content: "\f050"}.fa-step-forward:before{content: "\f051"}.fa-eject:before{content: "\f052"}.fa-chevron-left:before{content: "\f053"}.fa-chevron-right:before{content: "\f054"}.fa-plus-circle:before{content: "\f055"}.fa-minus-circle:before{content: "\f056"}.fa-times-circle:before{content: "\f057"}.fa-check-circle:before{content: "\f058"}.fa-question-circle:before{content: "\f059"}.fa-info-circle:before{content: "\f05a"}.fa-crosshairs:before{content: "\f05b"}.fa-times-circle-o:before{content: "\f05c"}.fa-check-circle-o:before{content: "\f05d"}.fa-ban:before{content: "\f05e"}.fa-arrow-left:before{content: "\f060"}.fa-arrow-right:before{content: "\f061"}.fa-arrow-up:before{content: "\f062"}.fa-arrow-down:before{content: "\f063"}.fa-mail-forward:before,.fa-share:before{content: "\f064"}.fa-expand:before{content: "\f065"}.fa-compress:before{content: "\f066"}.fa-plus:before{content: "\f067"}.fa-minus:before{content: "\f068"}.fa-asterisk:before{content: "\f069"}.fa-exclamation-circle:before{content: "\f06a"}.fa-gift:before{content: "\f06b"}.fa-leaf:before{content: "\f06c"}.fa-fire:before{content: "\f06d"}.fa-eye:before{content: "\f06e"}.fa-eye-slash:before{content: "\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content: "\f071"}.fa-plane:before{content: "\f072"}.fa-calendar:before{content: "\f073"}.fa-random:before{content: "\f074"}.fa-comment:before{content: "\f075"}.fa-magnet:before{content: "\f076"}.fa-chevron-up:before{content: "\f077"}.fa-chevron-down:before{content: "\f078"}.fa-retweet:before{content: "\f079"}.fa-shopping-cart:before{content: "\f07a"}.fa-folder:before{content: "\f07b"}.fa-folder-open:before{content: "\f07c"}.fa-arrows-v:before{content: "\f07d"}.fa-arrows-h:before{content: "\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content: "\f080"}.fa-twitter-square:before{content: "\f081"}.fa-facebook-square:before{content: "\f082"}.fa-camera-retro:before{content: "\f083"}.fa-key:before{content: "\f084"}.fa-gears:before,.fa-cogs:before{content: "\f085"}.fa-comments:before{content: "\f086"}.fa-thumbs-o-up:before{content: "\f087"}.fa-thumbs-o-down:before{content: "\f088"}.fa-star-half:before{content: "\f089"}.fa-heart-o:before{content: "\f08a"}.fa-sign-out:before{content: "\f08b"}.fa-linkedin-square:before{content: "\f08c"}.fa-thumb-tack:before{content: "\f08d"}.fa-external-link:before{content: "\f08e"}.fa-sign-in:before{content: "\f090"}.fa-trophy:before{content: "\f091"}.fa-github-square:before{content: "\f092"}.fa-upload:before{content: "\f093"}.fa-lemon-o:before{content: "\f094"}.fa-phone:before{content: "\f095"}.fa-square-o:before{content: "\f096"}.fa-bookmark-o:before{content: "\f097"}.fa-phone-square:before{content: "\f098"}.fa-twitter:before{content: "\f099"}.fa-facebook-f:before,.fa-facebook:before{content: "\f09a"}.fa-github:before{content: "\f09b"}.fa-unlock:before{content: "\f09c"}.fa-credit-card:before{content: "\f09d"}.fa-feed:before,.fa-rss:before{content: "\f09e"}.fa-hdd-o:before{content: "\f0a0"}.fa-bullhorn:before{content: "\f0a1"}.fa-bell:before{content: "\f0f3"}.fa-certificate:before{content: "\f0a3"}.fa-hand-o-right:before{content: "\f0a4"}.fa-hand-o-left:before{content: "\f0a5"}.fa-hand-o-up:before{content: "\f0a6"}.fa-hand-o-down:before{content: "\f0a7"}.fa-arrow-circle-left:before{content: "\f0a8"}.fa-arrow-circle-right:before{content: "\f0a9"}.fa-arrow-circle-up:before{content: "\f0aa"}.fa-arrow-circle-down:before{content: "\f0ab"}.fa-globe:before{content: "\f0ac"}.fa-wrench:before{content: "\f0ad"}.fa-tasks:before{content: "\f0ae"}.fa-filter:before{content: "\f0b0"}.fa-briefcase:before{content: "\f0b1"}.fa-arrows-alt:before{content: "\f0b2"}.fa-group:before,.fa-users:before{content: "\f0c0"}.fa-chain:before,.fa-link:before{content: "\f0c1"}.fa-cloud:before{content: "\f0c2"}.fa-flask:before{content: "\f0c3"}.fa-cut:before,.fa-scissors:before{content: "\f0c4"}.fa-copy:before,.fa-files-o:before{content: "\f0c5"}.fa-paperclip:before{content: "\f0c6"}.fa-save:before,.fa-floppy-o:before{content: "\f0c7"}.fa-square:before{content: "\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content: "\f0c9"}.fa-list-ul:before{content: "\f0ca"}.fa-list-ol:before{content: "\f0cb"}.fa-strikethrough:before{content: "\f0cc"}.fa-underline:before{content: "\f0cd"}.fa-table:before{content: "\f0ce"}.fa-magic:before{content: "\f0d0"}.fa-truck:before{content: "\f0d1"}.fa-pinterest:before{content: "\f0d2"}.fa-pinterest-square:before{content: "\f0d3"}.fa-google-plus-square:before{content: "\f0d4"}.fa-google-plus:before{content: "\f0d5"}.fa-money:before{content: "\f0d6"}.fa-caret-down:before{content: "\f0d7"}.fa-caret-up:before{content: "\f0d8"}.fa-caret-left:before{content: "\f0d9"}.fa-caret-right:before{content: "\f0da"}.fa-columns:before{content: "\f0db"}.fa-unsorted:before,.fa-sort:before{content: "\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content: "\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content: "\f0de"}.fa-envelope:before{content: "\f0e0"}.fa-linkedin:before{content: "\f0e1"}.fa-rotate-left:before,.fa-undo:before{content: "\f0e2"}.fa-legal:before,.fa-gavel:before{content: "\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content: "\f0e4"}.fa-comment-o:before{content: "\f0e5"}.fa-comments-o:before{content: "\f0e6"}.fa-flash:before,.fa-bolt:before{content: "\f0e7"}.fa-sitemap:before{content: "\f0e8"}.fa-umbrella:before{content: "\f0e9"}.fa-paste:before,.fa-clipboard:before{content: "\f0ea"}.fa-lightbulb-o:before{content: "\f0eb"}.fa-exchange:before{content: "\f0ec"}.fa-cloud-download:before{content: "\f0ed"}.fa-cloud-upload:before{content: "\f0ee"}.fa-user-md:before{content: "\f0f0"}.fa-stethoscope:before{content: "\f0f1"}.fa-suitcase:before{content: "\f0f2"}.fa-bell-o:before{content: "\f0a2"}.fa-coffee:before{content: "\f0f4"}.fa-cutlery:before{content: "\f0f5"}.fa-file-text-o:before{content: "\f0f6"}.fa-building-o:before{content: "\f0f7"}.fa-hospital-o:before{content: "\f0f8"}.fa-ambulance:before{content: "\f0f9"}.fa-medkit:before{content: "\f0fa"}.fa-fighter-jet:before{content: "\f0fb"}.fa-beer:before{content: "\f0fc"}.fa-h-square:before{content: "\f0fd"}.fa-plus-square:before{content: "\f0fe"}.fa-angle-double-left:before{content: "\f100"}.fa-angle-double-right:before{content: "\f101"}.fa-angle-double-up:before{content: "\f102"}.fa-angle-double-down:before{content: "\f103"}.fa-angle-left:before{content: "\f104"}.fa-angle-right:before{content: "\f105"}.fa-angle-up:before{content: "\f106"}.fa-angle-down:before{content: "\f107"}.fa-desktop:before{content: "\f108"}.fa-laptop:before{content: "\f109"}.fa-tablet:before{content: "\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content: "\f10b"}.fa-circle-o:before{content: "\f10c"}.fa-quote-left:before{content: "\f10d"}.fa-quote-right:before{content: "\f10e"}.fa-spinner:before{content: "\f110"}.fa-circle:before{content: "\f111"}.fa-mail-reply:before,.fa-reply:before{content: "\f112"}.fa-github-alt:before{content: "\f113"}.fa-folder-o:before{content: "\f114"}.fa-folder-open-o:before{content: "\f115"}.fa-smile-o:before{content: "\f118"}.fa-frown-o:before{content: "\f119"}.fa-meh-o:before{content: "\f11a"}.fa-gamepad:before{content: "\f11b"}.fa-keyboard-o:before{content: "\f11c"}.fa-flag-o:before{content: "\f11d"}.fa-flag-checkered:before{content: "\f11e"}.fa-terminal:before{content: "\f120"}.fa-code:before{content: "\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content: "\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content: "\f123"}.fa-location-arrow:before{content: "\f124"}.fa-crop:before{content: "\f125"}.fa-code-fork:before{content: "\f126"}.fa-unlink:before,.fa-chain-broken:before{content: "\f127"}.fa-question:before{content: "\f128"}.fa-info:before{content: "\f129"}.fa-exclamation:before{content: "\f12a"}.fa-superscript:before{content: "\f12b"}.fa-subscript:before{content: "\f12c"}.fa-eraser:before{content: "\f12d"}.fa-puzzle-piece:before{content: "\f12e"}.fa-microphone:before{content: "\f130"}.fa-microphone-slash:before{content: "\f131"}.fa-shield:before{content: "\f132"}.fa-calendar-o:before{content: "\f133"}.fa-fire-extinguisher:before{content: "\f134"}.fa-rocket:before{content: "\f135"}.fa-maxcdn:before{content: "\f136"}.fa-chevron-circle-left:before{content: "\f137"}.fa-chevron-circle-right:before{content: "\f138"}.fa-chevron-circle-up:before{content: "\f139"}.fa-chevron-circle-down:before{content: "\f13a"}.fa-html5:before{content: "\f13b"}.fa-css3:before{content: "\f13c"}.fa-anchor:before{content: "\f13d"}.fa-unlock-alt:before{content: "\f13e"}.fa-bullseye:before{content: "\f140"}.fa-ellipsis-h:before{content: "\f141"}.fa-ellipsis-v:before{content: "\f142"}.fa-rss-square:before{content: "\f143"}.fa-play-circle:before{content: "\f144"}.fa-ticket:before{content: "\f145"}.fa-minus-square:before{content: "\f146"}.fa-minus-square-o:before{content: "\f147"}.fa-level-up:before{content: "\f148"}.fa-level-down:before{content: "\f149"}.fa-check-square:before{content: "\f14a"}.fa-pencil-square:before{content: "\f14b"}.fa-external-link-square:before{content: "\f14c"}.fa-share-square:before{content: "\f14d"}.fa-compass:before{content: "\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content: "\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content: "\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content: "\f152"}.fa-euro:before,.fa-eur:before{content: "\f153"}.fa-gbp:before{content: "\f154"}.fa-dollar:before,.fa-usd:before{content: "\f155"}.fa-rupee:before,.fa-inr:before{content: "\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content: "\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content: "\f158"}.fa-won:before,.fa-krw:before{content: "\f159"}.fa-bitcoin:before,.fa-btc:before{content: "\f15a"}.fa-file:before{content: "\f15b"}.fa-file-text:before{content: "\f15c"}.fa-sort-alpha-asc:before{content: "\f15d"}.fa-sort-alpha-desc:before{content: "\f15e"}.fa-sort-amount-asc:before{content: "\f160"}.fa-sort-amount-desc:before{content: "\f161"}.fa-sort-numeric-asc:before{content: "\f162"}.fa-sort-numeric-desc:before{content: "\f163"}.fa-thumbs-up:before{content: "\f164"}.fa-thumbs-down:before{content: "\f165"}.fa-youtube-square:before{content: "\f166"}.fa-youtube:before{content: "\f167"}.fa-xing:before{content: "\f168"}.fa-xing-square:before{content: "\f169"}.fa-youtube-play:before{content: "\f16a"}.fa-dropbox:before{content: "\f16b"}.fa-stack-overflow:before{content: "\f16c"}.fa-instagram:before{content: "\f16d"}.fa-flickr:before{content: "\f16e"}.fa-adn:before{content: "\f170"}.fa-bitbucket:before{content: "\f171"}.fa-bitbucket-square:before{content: "\f172"}.fa-tumblr:before{content: "\f173"}.fa-tumblr-square:before{content: "\f174"}.fa-long-arrow-down:before{content: "\f175"}.fa-long-arrow-up:before{content: "\f176"}.fa-long-arrow-left:before{content: "\f177"}.fa-long-arrow-right:before{content: "\f178"}.fa-apple:before{content: "\f179"}.fa-windows:before{content: "\f17a"}.fa-android:before{content: "\f17b"}.fa-linux:before{content: "\f17c"}.fa-dribbble:before{content: "\f17d"}.fa-skype:before{content: "\f17e"}.fa-foursquare:before{content: "\f180"}.fa-trello:before{content: "\f181"}.fa-female:before{content: "\f182"}.fa-male:before{content: "\f183"}.fa-gittip:before,.fa-gratipay:before{content: "\f184"}.fa-sun-o:before{content: "\f185"}.fa-moon-o:before{content: "\f186"}.fa-archive:before{content: "\f187"}.fa-bug:before{content: "\f188"}.fa-vk:before{content: "\f189"}.fa-weibo:before{content: "\f18a"}.fa-renren:before{content: "\f18b"}.fa-pagelines:before{content: "\f18c"}.fa-stack-exchange:before{content: "\f18d"}.fa-arrow-circle-o-right:before{content: "\f18e"}.fa-arrow-circle-o-left:before{content: "\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content: "\f191"}.fa-dot-circle-o:before{content: "\f192"}.fa-wheelchair:before{content: "\f193"}.fa-vimeo-square:before{content: "\f194"}.fa-turkish-lira:before,.fa-try:before{content: "\f195"}.fa-plus-square-o:before{content: "\f196"}.fa-space-shuttle:before{content: "\f197"}.fa-slack:before{content: "\f198"}.fa-envelope-square:before{content: "\f199"}.fa-wordpress:before{content: "\f19a"}.fa-openid:before{content: "\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content: "\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content: "\f19d"}.fa-yahoo:before{content: "\f19e"}.fa-google:before{content: "\f1a0"}.fa-reddit:before{content: "\f1a1"}.fa-reddit-square:before{content: "\f1a2"}.fa-stumbleupon-circle:before{content: "\f1a3"}.fa-stumbleupon:before{content: "\f1a4"}.fa-delicious:before{content: "\f1a5"}.fa-digg:before{content: "\f1a6"}.fa-pied-piper-pp:before{content: "\f1a7"}.fa-pied-piper-alt:before{content: "\f1a8"}.fa-drupal:before{content: "\f1a9"}.fa-joomla:before{content: "\f1aa"}.fa-language:before{content: "\f1ab"}.fa-fax:before{content: "\f1ac"}.fa-building:before{content: "\f1ad"}.fa-child:before{content: "\f1ae"}.fa-paw:before{content: "\f1b0"}.fa-spoon:before{content: "\f1b1"}.fa-cube:before{content: "\f1b2"}.fa-cubes:before{content: "\f1b3"}.fa-behance:before{content: "\f1b4"}.fa-behance-square:before{content: "\f1b5"}.fa-steam:before{content: "\f1b6"}.fa-steam-square:before{content: "\f1b7"}.fa-recycle:before{content: "\f1b8"}.fa-automobile:before,.fa-car:before{content: "\f1b9"}.fa-cab:before,.fa-taxi:before{content: "\f1ba"}.fa-tree:before{content: "\f1bb"}.fa-spotify:before{content: "\f1bc"}.fa-deviantart:before{content: "\f1bd"}.fa-soundcloud:before{content: "\f1be"}.fa-database:before{content: "\f1c0"}.fa-file-pdf-o:before{content: "\f1c1"}.fa-file-word-o:before{content: "\f1c2"}.fa-file-excel-o:before{content: "\f1c3"}.fa-file-powerpoint-o:before{content: "\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content: "\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content: "\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content: "\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content: "\f1c8"}.fa-file-code-o:before{content: "\f1c9"}.fa-vine:before{content: "\f1ca"}.fa-codepen:before{content: "\f1cb"}.fa-jsfiddle:before{content: "\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content: "\f1cd"}.fa-circle-o-notch:before{content: "\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content: "\f1d0"}.fa-ge:before,.fa-empire:before{content: "\f1d1"}.fa-git-square:before{content: "\f1d2"}.fa-git:before{content: "\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content: "\f1d4"}.fa-tencent-weibo:before{content: "\f1d5"}.fa-qq:before{content: "\f1d6"}.fa-wechat:before,.fa-weixin:before{content: "\f1d7"}.fa-send:before,.fa-paper-plane:before{content: "\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content: "\f1d9"}.fa-history:before{content: "\f1da"}.fa-circle-thin:before{content: "\f1db"}.fa-header:before{content: "\f1dc"}.fa-paragraph:before{content: "\f1dd"}.fa-sliders:before{content: "\f1de"}.fa-share-alt:before{content: "\f1e0"}.fa-share-alt-square:before{content: "\f1e1"}.fa-bomb:before{content: "\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content: "\f1e3"}.fa-tty:before{content: "\f1e4"}.fa-binoculars:before{content: "\f1e5"}.fa-plug:before{content: "\f1e6"}.fa-slideshare:before{content: "\f1e7"}.fa-twitch:before{content: "\f1e8"}.fa-yelp:before{content: "\f1e9"}.fa-newspaper-o:before{content: "\f1ea"}.fa-wifi:before{content: "\f1eb"}.fa-calculator:before{content: "\f1ec"}.fa-paypal:before{content: "\f1ed"}.fa-google-wallet:before{content: "\f1ee"}.fa-cc-visa:before{content: "\f1f0"}.fa-cc-mastercard:before{content: "\f1f1"}.fa-cc-discover:before{content: "\f1f2"}.fa-cc-amex:before{content: "\f1f3"}.fa-cc-paypal:before{content: "\f1f4"}.fa-cc-stripe:before{content: "\f1f5"}.fa-bell-slash:before{content: "\f1f6"}.fa-bell-slash-o:before{content: "\f1f7"}.fa-trash:before{content: "\f1f8"}.fa-copyright:before{content: "\f1f9"}.fa-at:before{content: "\f1fa"}.fa-eyedropper:before{content: "\f1fb"}.fa-paint-brush:before{content: "\f1fc"}.fa-birthday-cake:before{content: "\f1fd"}.fa-area-chart:before{content: "\f1fe"}.fa-pie-chart:before{content: "\f200"}.fa-line-chart:before{content: "\f201"}.fa-lastfm:before{content: "\f202"}.fa-lastfm-square:before{content: "\f203"}.fa-toggle-off:before{content: "\f204"}.fa-toggle-on:before{content: "\f205"}.fa-bicycle:before{content: "\f206"}.fa-bus:before{content: "\f207"}.fa-ioxhost:before{content: "\f208"}.fa-angellist:before{content: "\f209"}.fa-cc:before{content: "\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content: "\f20b"}.fa-meanpath:before{content: "\f20c"}.fa-buysellads:before{content: "\f20d"}.fa-connectdevelop:before{content: "\f20e"}.fa-dashcube:before{content: "\f210"}.fa-forumbee:before{content: "\f211"}.fa-leanpub:before{content: "\f212"}.fa-sellsy:before{content: "\f213"}.fa-shirtsinbulk:before{content: "\f214"}.fa-simplybuilt:before{content: "\f215"}.fa-skyatlas:before{content: "\f216"}.fa-cart-plus:before{content: "\f217"}.fa-cart-arrow-down:before{content: "\f218"}.fa-diamond:before{content: "\f219"}.fa-ship:before{content: "\f21a"}.fa-user-secret:before{content: "\f21b"}.fa-motorcycle:before{content: "\f21c"}.fa-street-view:before{content: "\f21d"}.fa-heartbeat:before{content: "\f21e"}.fa-venus:before{content: "\f221"}.fa-mars:before{content: "\f222"}.fa-mercury:before{content: "\f223"}.fa-intersex:before,.fa-transgender:before{content: "\f224"}.fa-transgender-alt:before{content: "\f225"}.fa-venus-double:before{content: "\f226"}.fa-mars-double:before{content: "\f227"}.fa-venus-mars:before{content: "\f228"}.fa-mars-stroke:before{content: "\f229"}.fa-mars-stroke-v:before{content: "\f22a"}.fa-mars-stroke-h:before{content: "\f22b"}.fa-neuter:before{content: "\f22c"}.fa-genderless:before{content: "\f22d"}.fa-facebook-official:before{content: "\f230"}.fa-pinterest-p:before{content: "\f231"}.fa-whatsapp:before{content: "\f232"}.fa-server:before{content: "\f233"}.fa-user-plus:before{content: "\f234"}.fa-user-times:before{content: "\f235"}.fa-hotel:before,.fa-bed:before{content: "\f236"}.fa-viacoin:before{content: "\f237"}.fa-train:before{content: "\f238"}.fa-subway:before{content: "\f239"}.fa-medium:before{content: "\f23a"}.fa-yc:before,.fa-y-combinator:before{content: "\f23b"}.fa-optin-monster:before{content: "\f23c"}.fa-opencart:before{content: "\f23d"}.fa-expeditedssl:before{content: "\f23e"}.fa-battery-4:before,.fa-battery-full:before{content: "\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content: "\f241"}.fa-battery-2:before,.fa-battery-half:before{content: "\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content: "\f243"}.fa-battery-0:before,.fa-battery-empty:before{content: "\f244"}.fa-mouse-pointer:before{content: "\f245"}.fa-i-cursor:before{content: "\f246"}.fa-object-group:before{content: "\f247"}.fa-object-ungroup:before{content: "\f248"}.fa-sticky-note:before{content: "\f249"}.fa-sticky-note-o:before{content: "\f24a"}.fa-cc-jcb:before{content: "\f24b"}.fa-cc-diners-club:before{content: "\f24c"}.fa-clone:before{content: "\f24d"}.fa-balance-scale:before{content: "\f24e"}.fa-hourglass-o:before{content: "\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content: "\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content: "\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content: "\f253"}.fa-hourglass:before{content: "\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content: "\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content: "\f256"}.fa-hand-scissors-o:before{content: "\f257"}.fa-hand-lizard-o:before{content: "\f258"}.fa-hand-spock-o:before{content: "\f259"}.fa-hand-pointer-o:before{content: "\f25a"}.fa-hand-peace-o:before{content: "\f25b"}.fa-trademark:before{content: "\f25c"}.fa-registered:before{content: "\f25d"}.fa-creative-commons:before{content: "\f25e"}.fa-gg:before{content: "\f260"}.fa-gg-circle:before{content: "\f261"}.fa-tripadvisor:before{content: "\f262"}.fa-odnoklassniki:before{content: "\f263"}.fa-odnoklassniki-square:before{content: "\f264"}.fa-get-pocket:before{content: "\f265"}.fa-wikipedia-w:before{content: "\f266"}.fa-safari:before{content: "\f267"}.fa-chrome:before{content: "\f268"}.fa-firefox:before{content: "\f269"}.fa-opera:before{content: "\f26a"}.fa-internet-explorer:before{content: "\f26b"}.fa-tv:before,.fa-television:before{content: "\f26c"}.fa-contao:before{content: "\f26d"}.fa-500px:before{content: "\f26e"}.fa-amazon:before{content: "\f270"}.fa-calendar-plus-o:before{content: "\f271"}.fa-calendar-minus-o:before{content: "\f272"}.fa-calendar-times-o:before{content: "\f273"}.fa-calendar-check-o:before{content: "\f274"}.fa-industry:before{content: "\f275"}.fa-map-pin:before{content: "\f276"}.fa-map-signs:before{content: "\f277"}.fa-map-o:before{content: "\f278"}.fa-map:before{content: "\f279"}.fa-commenting:before{content: "\f27a"}.fa-commenting-o:before{content: "\f27b"}.fa-houzz:before{content: "\f27c"}.fa-vimeo:before{content: "\f27d"}.fa-black-tie:before{content: "\f27e"}.fa-fonticons:before{content: "\f280"}.fa-reddit-alien:before{content: "\f281"}.fa-edge:before{content: "\f282"}.fa-credit-card-alt:before{content: "\f283"}.fa-codiepie:before{content: "\f284"}.fa-modx:before{content: "\f285"}.fa-fort-awesome:before{content: "\f286"}.fa-usb:before{content: "\f287"}.fa-product-hunt:before{content: "\f288"}.fa-mixcloud:before{content: "\f289"}.fa-scribd:before{content: "\f28a"}.fa-pause-circle:before{content: "\f28b"}.fa-pause-circle-o:before{content: "\f28c"}.fa-stop-circle:before{content: "\f28d"}.fa-stop-circle-o:before{content: "\f28e"}.fa-shopping-bag:before{content: "\f290"}.fa-shopping-basket:before{content: "\f291"}.fa-hashtag:before{content: "\f292"}.fa-bluetooth:before{content: "\f293"}.fa-bluetooth-b:before{content: "\f294"}.fa-percent:before{content: "\f295"}.fa-gitlab:before{content: "\f296"}.fa-wpbeginner:before{content: "\f297"}.fa-wpforms:before{content: "\f298"}.fa-envira:before{content: "\f299"}.fa-universal-access:before{content: "\f29a"}.fa-wheelchair-alt:before{content: "\f29b"}.fa-question-circle-o:before{content: "\f29c"}.fa-blind:before{content: "\f29d"}.fa-audio-description:before{content: "\f29e"}.fa-volume-control-phone:before{content: "\f2a0"}.fa-braille:before{content: "\f2a1"}.fa-assistive-listening-systems:before{content: "\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content: "\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content: "\f2a4"}.fa-glide:before{content: "\f2a5"}.fa-glide-g:before{content: "\f2a6"}.fa-signing:before,.fa-sign-language:before{content: "\f2a7"}.fa-low-vision:before{content: "\f2a8"}.fa-viadeo:before{content: "\f2a9"}.fa-viadeo-square:before{content: "\f2aa"}.fa-snapchat:before{content: "\f2ab"}.fa-snapchat-ghost:before{content: "\f2ac"}.fa-snapchat-square:before{content: "\f2ad"}.fa-pied-piper:before{content: "\f2ae"}.fa-first-order:before{content: "\f2b0"}.fa-yoast:before{content: "\f2b1"}.fa-themeisle:before{content: "\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content: "\f2b3"}.fa-fa:before,.fa-font-awesome:before{content: "\f2b4"}.sr-only{position: absolute;width: 1px;height: 1px;padding: 0;margin: -1px;overflow: hidden;clip: rect(0, 0, 0, 0);border: 0}.sr-only-focusable:active,.sr-only-focusable:focus{position: static;width: auto;height: auto;margin: 0;overflow: visible;clip: auto} \ No newline at end of file diff --git a/public/js/zettlr.editor.js b/public/js/zettlr.editor.js index 238338c..2b42f03 100644 --- a/public/js/zettlr.editor.js +++ b/public/js/zettlr.editor.js @@ -19,11 +19,12 @@ window.addEventListener('load', function() { // Save the current DOM contents to server newContents = { _token: zettlrURL.csrfToken, + api_token: zettlrURL.api_token, title: $('[data-name="page-title"]').text(), content: regions['page-content'], slug: $('[data-name="page-content"]').attr("data-slug") }; - + $.ajax({ method: 'POST', url: zettlrURL.contentToolsSetter, diff --git a/public/js/zettlrWiki.js b/public/js/zettlrWiki.js index 0a6a09c..f60d586 100644 --- a/public/js/zettlrWiki.js +++ b/public/js/zettlrWiki.js @@ -19,11 +19,13 @@ window.addEventListener('load', function() { // Save the current DOM contents to server newContents = { _token: zettlrURL.csrfToken, + api_token: zettlrURL.api_token, title: $('[data-name="page-title"]').text(), content: regions['page-content'], slug: $('[data-name="page-content"]').attr("data-slug") }; +console.log('Using API token ' + zettlrURL.api_token); $.ajax({ method: 'POST', url: zettlrURL.contentToolsSetter, diff --git a/public/js/zettlrWiki.min.js b/public/js/zettlrWiki.min.js index 4c2787e..dac599b 100644 --- a/public/js/zettlrWiki.min.js +++ b/public/js/zettlrWiki.min.js @@ -1,2 +1,2 @@ -/* zettlrWiki 2016-09-10 */ -function MediaLibrary(a,b,c){this.filesToUpload=[],this.getterURL=a,this.setterURL=b,this.token=c,this.isReady=!1,this.dz=null,this.dzelement='
blablabla
Ok
Error
',this.modal=$("
",{id:"media-library",html:'

Media library

Loading …
Upload files Close
'}).addClass("modal"),this.modal.style="display:none"}window.addEventListener("load",function(){var a=ContentTools.EditorApp.get();a.init("*[data-editable]","data-name",null,!1),a.addEventListener("saved",function(b){a.busy(!0),regions=b.detail().regions,newContents={_token:zettlrURL.csrfToken,title:$('[data-name="page-title"]').text(),content:regions["page-content"],slug:$('[data-name="page-content"]').attr("data-slug")},$.ajax({method:"POST",url:zettlrURL.contentToolsSetter,data:newContents,success:function(b){$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id"),function(){}).success(function(b){a.busy(!1),$('[data-name="page-content"]').html(b[0]),assembleTOC(),$("#contenttoolsEdit").html(""+zettlrTrans.saveSuccess+"").removeClass("warning").addClass("success").removeClass("editing"),$("#contenttoolsCancel").addClass("hidden"),$("#contenttoolsEdit span").first().fadeOut(1200,function(){$(this).text(zettlrTrans.editContentTools).fadeIn(100)})}).fail(function(a){new ContentTools.FlashUI("no")})},error:function(a){new ContentTools.FlashUI("no"),$("#contenttoolsEdit").text(zettlrTrans.saveFail).removeClass("warning").addClass("error")}})}),$("#contenttoolsEdit").on("click",function(){$(this).hasClass("editing")?($("#contenttoolsEdit").html('').addClass("warning").removeClass("success"),a.stop(!0)):($("#contenttoolsEdit").html('').addClass("warning").removeClass("success"),a.busy(!0),$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id")+"/raw",function(){}).success(function(b){$('[data-name="page-content"]').html(b[0]),a.syncRegions('[data-name="page-content"]'),a.busy(!1),a.start(),$("#contenttoolsEdit").html(zettlrTrans.saveChanges).addClass("success").removeClass("warning").addClass("editing"),$("#contenttoolsCancel").removeClass("hidden")}).fail(function(a){new ContentTools.FlashUI("no")}))}),$("#contenttoolsCancel").on("click",function(){$("#contenttoolsEdit").hasClass("editing")&&($("#contenttoolsCancel").html('').addClass("warning").removeClass("error"),a.busy(!0),a.stop(!1),$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id"),function(){}).success(function(b){$('[data-name="page-content"]').html(b[0]),a.busy(!1),$("#contenttoolsEdit").html(zettlrTrans.editContentTools).removeClass("editing"),$("#contenttoolsCancel").addClass("hidden").removeClass("warning").addClass("error").html(zettlrTrans.cancel),assembleTOC()}).fail(function(a){new ContentTools.FlashUI("no")}))})});var decodeHtmlEntity=function(a){return a.replace(/&#(\d+);/g,function(a,b){return String.fromCharCode(b)})},encodeHtmlEntity=function(a){for(var b=[],c=a.length-1;c>=0;c--)b.unshift(["&#",a[c].charCodeAt(),";"].join(""));return b.join("")},assembleTOC=function(){$(".toc-parent").length&&$(".toc-parent").remove(),$("#toc-toggle").length&&$("#toc-toggle").first().parent().remove();var a=[],b=0;if($("#wikitext").find("*").each(function(){$(this).prop("tagName").match(/^h[1-6]$/i)&&(txt=encodeHtmlEntity($(this).text()),a.push('
  • '+txt+"
  • "),$(this).prepend(''),b++)}),a.length>0){for(mydiv=$("body").append('
    '),mydiv=$("#toc ul"),$("nav.nav ul").prepend('
  • '),b=0;b'+ttipcontent+""),ttipelem.css("top",$(this).offset().top-4),ttipelem.css("left",$(this).offset().left+$(this).outerWidth()+10),$("body").append(ttipelem).fadeIn("fast")}),$(".toc-link").mouseout(function(){ttipid="ttip-"+$(this).attr("href").substring(1),$("#"+ttipid).each(function(){$(this).fadeOut("fast",function(){$(this).remove()})})})}};MediaLibrary.prototype.init=function(){this.on("addedfile",function(a){titleElem=Dropzone.createElement(''),a.previewElement.appendChild(titleElem),descElem=Dropzone.createElement(''),a.previewElement.appendChild(descElem),clearElem=Dropzone.createElement('
    '),a.previewElement.appendChild(clearElem);var b=this;titleElem.addEventListener("keypress",function(a){b.isReady=!0,$(".title-input-field").each(function(){""===$(this).val()&&(b.isReady=!1)}),b.isReady?($("#media-library-upload-button").removeClass("muted"),$("#media-library-upload-button").addClass("success")):($("#media-library-upload-button").removeClass("success"),$("#media-library-upload-button").addClass("muted"))}),$(".title-input-field").trigger("keypress")})},MediaLibrary.prototype.show=function(){$("body").addClass("dim"),$("body").append(this.modal),$("#media-library").show(),$(document).keydown($.proxy(function(a){27==a.which&&this.close(a)},this)),$("#media-library-close").click($.proxy(this.close,this)),$("head").append(''),this.dz=new Dropzone("div#dropzone",{url:this.setterURL,paramName:"file",maxFilesize:8,uploadMultiple:!0,addRemoveLinks:!0,clickable:!0,acceptedFiles:"image/jpg,image/jpeg,image/png,image/gif,image/svg,image/bmp",autoProcessQueue:!1,previewElement:this.dzelement,init:this.init}),$("#media-library").tabs(),$("#media-library-upload-button").hide(),$(".tab-nav").bind("click",function(a){1==$("#media-library").tabs("option","active")?$("#media-library-upload-button").show():$("#media-library-upload-button").hide()});var a=this;$("#media-library-upload-button").click(function(b){$(this).is(":visible")&&a.dz.isReady&&a.dz.processQueue()}),this.dz.on("sending",function(a,b,c){c.append("userName","bob")})},MediaLibrary.prototype.close=function(a){$("#media-library").is(":visible")&&($("body").removeClass("dim"),$("#media-library").remove(),$("#dzjs").remove())},MediaLibrary.prototype.getMedia=function(){$.get(this.getterURL,function(){}).done(function(a){for(var b=0;b'+a.responseJSON.message+"")})},MediaLibrary.prototype.processSelection=function(){this.validate&&this.dz.processQueue()},MediaLibrary.prototype.validate=function(a){return console.log("Validating ..."),disabled=!0,$(".media-library-file").each(function(){}),disabled?$("#media-library-submit").attr("disabled","disabled"):$("#media-library-submit").removeAttr("disabled"),!1}; \ No newline at end of file +/* zettlrWiki 2016-09-11 */ +function MediaLibrary(a,b,c){this.filesToUpload=[],this.getterURL=a,this.setterURL=b,this.token=c,this.isReady=!1,this.dz=null,this.dzelement='
    blablabla
    Ok
    Error
    ',this.modal=$("
    ",{id:"media-library",html:' '}).addClass("modal"),this.modal.style="display:none"}window.addEventListener("load",function(){var a=ContentTools.EditorApp.get();a.init("*[data-editable]","data-name",null,!1),a.addEventListener("saved",function(b){a.busy(!0),regions=b.detail().regions,newContents={_token:zettlrURL.csrfToken,api_token:zettlrURL.api_token,title:$('[data-name="page-title"]').text(),content:regions["page-content"],slug:$('[data-name="page-content"]').attr("data-slug")},console.log("Using API token "+zettlrURL.api_token),$.ajax({method:"POST",url:zettlrURL.contentToolsSetter,data:newContents,success:function(b){$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id"),function(){}).success(function(b){a.busy(!1),$('[data-name="page-content"]').html(b[0]),assembleTOC(),$("#contenttoolsEdit").html(""+zettlrTrans.saveSuccess+"").removeClass("warning").addClass("success").removeClass("editing"),$("#contenttoolsCancel").addClass("hidden"),$("#contenttoolsEdit span").first().fadeOut(1200,function(){$(this).text(zettlrTrans.editContentTools).fadeIn(100)})}).fail(function(a){new ContentTools.FlashUI("no")})},error:function(a){new ContentTools.FlashUI("no"),$("#contenttoolsEdit").text(zettlrTrans.saveFail).removeClass("warning").addClass("error")}})}),$("#contenttoolsEdit").on("click",function(){$(this).hasClass("editing")?($("#contenttoolsEdit").html('').addClass("warning").removeClass("success"),a.stop(!0)):($("#contenttoolsEdit").html('').addClass("warning").removeClass("success"),a.busy(!0),$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id")+"/raw",function(){}).success(function(b){$('[data-name="page-content"]').html(b[0]),a.syncRegions('[data-name="page-content"]'),a.busy(!1),a.start(),$("#contenttoolsEdit").html(zettlrTrans.saveChanges).addClass("success").removeClass("warning").addClass("editing"),$("#contenttoolsCancel").removeClass("hidden")}).fail(function(a){new ContentTools.FlashUI("no")}))}),$("#contenttoolsCancel").on("click",function(){$("#contenttoolsEdit").hasClass("editing")&&($("#contenttoolsCancel").html('').addClass("warning").removeClass("error"),a.busy(!0),a.stop(!1),$.get(zettlrURL.contentToolsGetter+"/"+$('[data-name="page-content"]').attr("data-id"),function(){}).success(function(b){$('[data-name="page-content"]').html(b[0]),a.busy(!1),$("#contenttoolsEdit").html(zettlrTrans.editContentTools).removeClass("editing"),$("#contenttoolsCancel").addClass("hidden").removeClass("warning").addClass("error").html(zettlrTrans.cancel),assembleTOC()}).fail(function(a){new ContentTools.FlashUI("no")}))})});var decodeHtmlEntity=function(a){return a.replace(/&#(\d+);/g,function(a,b){return String.fromCharCode(b)})},encodeHtmlEntity=function(a){for(var b=[],c=a.length-1;c>=0;c--)b.unshift(["&#",a[c].charCodeAt(),";"].join(""));return b.join("")},assembleTOC=function(){$(".toc-parent").length&&$(".toc-parent").remove(),$("#toc-toggle").length&&$("#toc-toggle").first().parent().remove();var a=[],b=0;if($("#wikitext").find("*").each(function(){$(this).prop("tagName").match(/^h[1-6]$/i)&&(txt=encodeHtmlEntity($(this).text()),a.push('
  • '+txt+"
  • "),$(this).prepend(''),b++)}),a.length>0){for(mydiv=$("body").append('
      '),mydiv=$("#toc ul"),$("nav.nav ul").prepend('
    • '),b=0;b'+ttipcontent+""),ttipelem.css("top",$(this).offset().top-4),ttipelem.css("left",$(this).offset().left+$(this).outerWidth()+10),$("body").append(ttipelem).fadeIn("fast")}),$(".toc-link").mouseout(function(){ttipid="ttip-"+$(this).attr("href").substring(1),$("#"+ttipid).each(function(){$(this).fadeOut("fast",function(){$(this).remove()})})})}};MediaLibrary.prototype.init=function(){this.on("addedfile",function(a){titleElem=Dropzone.createElement(''),a.previewElement.appendChild(titleElem),descElem=Dropzone.createElement(''),a.previewElement.appendChild(descElem),clearElem=Dropzone.createElement('
      '),a.previewElement.appendChild(clearElem);var b=this;titleElem.addEventListener("keypress",function(a){b.isReady=!0,$(".title-input-field").each(function(){""===$(this).val()&&(b.isReady=!1)}),b.isReady?($("#media-library-upload-button").removeClass("muted"),$("#media-library-upload-button").addClass("success")):($("#media-library-upload-button").removeClass("success"),$("#media-library-upload-button").addClass("muted"))}),$(".title-input-field").trigger("keypress")})},MediaLibrary.prototype.show=function(){$("body").addClass("dim"),$("body").append(this.modal),$("#media-library").show(),$(document).keydown($.proxy(function(a){27==a.which&&this.close(a)},this)),$("#media-library-close").click($.proxy(this.close,this)),$("head").append(''),this.dz=new Dropzone("div#dropzone",{url:this.setterURL,paramName:"file",maxFilesize:8,uploadMultiple:!0,addRemoveLinks:!0,clickable:!0,acceptedFiles:"image/jpg,image/jpeg,image/png,image/gif,image/svg,image/bmp",autoProcessQueue:!1,previewElement:this.dzelement,init:this.init}),$("#media-library").tabs(),$("#media-library-upload-button").hide(),$(".tab-nav").bind("click",function(a){1==$("#media-library").tabs("option","active")?$("#media-library-upload-button").show():$("#media-library-upload-button").hide()});var a=this;$("#media-library-upload-button").click(function(b){$(this).is(":visible")&&a.dz.isReady&&a.dz.processQueue()}),this.dz.on("sending",function(a,b,c){c.append("userName","bob")})},MediaLibrary.prototype.close=function(a){$("#media-library").is(":visible")&&($("body").removeClass("dim"),$("#media-library").remove(),$("#dzjs").remove())},MediaLibrary.prototype.getMedia=function(){$.get(this.getterURL,function(){}).done(function(a){for(var b=0;b'+a.responseJSON.message+"")})},MediaLibrary.prototype.processSelection=function(){this.validate&&this.dz.processQueue()},MediaLibrary.prototype.validate=function(a){return console.log("Validating ..."),disabled=!0,$(".media-library-file").each(function(){}),disabled?$("#media-library-submit").attr("disabled","disabled"):$("#media-library-submit").removeAttr("disabled"),!1}; \ No newline at end of file diff --git a/resources/assets/less/typography.less b/resources/assets/less/typography.less index 9da4c71..c618ef6 100644 --- a/resources/assets/less/typography.less +++ b/resources/assets/less/typography.less @@ -72,7 +72,7 @@ table.log { padding:0px; border-collapse:collapse; font-family:@font-family-monospace; - font-weight:light; + font-weight:200; font-size:@font-size-basic * 0.7; tr { @@ -84,13 +84,14 @@ table.log { padding:4px; &.line { - background-color:@color-dark; + background-color:black; color:@color-white; + vertical-align:top; } &.content { - background-color:@color-darker; - color:@color-black; + background-color:@color-dark; + color:@color-white; } } } diff --git a/resources/lang/de/ui.php b/resources/lang/de/ui.php index ea0f8c4..ccc0163 100644 --- a/resources/lang/de/ui.php +++ b/resources/lang/de/ui.php @@ -53,10 +53,46 @@ 'index' => 'Seitenindex', 'create' => 'Neue Seite erstellen', 'backend' => 'Administrationsbereich', - 'search' => 'Finde in der Wiki …', + 'search' => 'Finden …', 'top' => 'Nach oben', ], 'backend' => [ + 'user' => [ + 'welcome' => 'Willkommen zurück, :user!', + 'login' => 'Einloggen', + 'logout' => 'Ausloggen', + 'register' => 'Registrierung', + 'no_account' => 'Noch kein Nutzerkonto? Lege dir eines an:', + 'information' => 'Nutzerinformationen', + 'name' => 'Benutzername', + 'email' => 'Email-Adresse', + 'name_or_mail' => 'Benutzername oder Email-Adresse', + 'password' => 'Passwort', + 'password_confirm' => 'Passwort bestätigen', + 'token' => 'Um dich zu registrieren, musst du einen Schlüssel eingeben. Diesen solltest du bereits erhalten haben.', + 'token_input' => 'Schlüssel eingeben', + 'register_submit' => 'Registrierung abschließen', + 'account' => 'Benutzerkonto', + 'accountinfo' => 'Auf dieser Seite kannst du deine Benutzerdaten wie Email-Adresse oder Name ändern. Außerdem kannst du hier dein API-Token erneuern.', + 'api_token' => 'API-Token', + 'api_token_info' => 'Dein Momentanes API-Token ist :token. Erstelle es neu, falls du auf Probleme stößt. Beispielsweise, wenn du nicht mehr in der Lage bist, via ContentTools Seiten zu editieren.', + 'api_token_regen' => 'API-Token neu erstellen', + 'pw_verify' => 'Bitte bestätige dein aktuelles Passwort.', + ], + 'token' => 'Registrierungsschlüssel', + 'tokeninfo' => 'Diese Seite listet alle vorhandenen + Schlüssel auf, mit denen sich Benutzer + im eingeschränkten Modus registrieren + können. Möchtest du jemandem die + Registrierung erlauben, so lege einen + Schlüssel mit mindestens einer Benutzung + an, und gib ihn dem Nutzer. Er verfällt + dann automatisch nach der Anmeldung.', + 'token_remaining' => 'Verbleibende Nutzungen', + 'create_token' => 'Neue Schüssel anlegen', + 'token_amount' => 'Anzahl neuer Schlüssel', + 'token_uses' => 'Verwendungen der neuen Schlüssel', + 'tokenplaceholder' => 'Menge', 'backtomain' => 'Zurück zur Startseite', 'dashboard' => 'Dashboard', 'settings' => 'Einstellungen', @@ -65,8 +101,8 @@ 'pages' => 'Seiten in dieser Wiki', 'dbsize' => 'Momentane Datenbankgröße', 'dbbackup' => 'Datenbank sichern', - 'viewcache' => 'Größe des View-Caches', - 'viewflush' => 'Views-Cache leeren', + 'cache' => 'Größe des Caches', + 'cacheflush' => 'Cache leeren', 'exporthtmldesc' => 'Alle Seiten im HTML-Format als ZIP exportieren', 'exporthtmllink' => 'Exportieren …', ], @@ -74,6 +110,8 @@ 'lognone' => 'Keine Logdateien gefunden!', 'logs' => 'Logdateien', 'availablelogs' => 'Verfügbare Logdateien', + 'logfull' => 'Vollständiges Log ansehen', + 'logtail' => 'Nur das Ende des Logs anzeigen', ], 'settings' => [ 'settings' => 'Einstellungen', @@ -94,6 +132,21 @@ 'en' => 'Englisch (USA)', 'de' => 'Deutsch (Deutschland)', ], + 'auth' => [ + 'title' => 'Benutzer und Sicherheit', + 'active' => 'Authentifizierungssystem aktivieren', + 'activeinfo' => 'Wenn du das Authentifizierungssystem aktivierst, musst du dich je nach Einstellungen einloggen, um diese Seite zu sehen. Sei vorsichtig mit dieser Einstellung, damit du dich nicht selber ausschließt!', + 'isactive' => 'Aktiv', + 'isnotactive' => 'Ausgeschaltet', + 'register' => 'Registrierung', + 'registerinfo' => 'Hiermit kannst du steuern, ob sich neue Nutzer frei registrieren können, oder ob sie eingeladen werden müssen.', + 'registeractive' => 'Jeder kann sich registrieren', + 'registerinactive' => 'Registrierungen sind nur mit einem Schlüssel möglich.', + 'guest_edit' => 'Gäste-Bearbeitungen', + 'guest_editinfo' => 'Mit dieser Option kannst du es Gästen und nicht eingeloggten Benutzern erlauben oder verbieten, Seiten zu bearbeiten.', + 'guest_editinactive'=> 'Nur registrierte und eingeloggte Benutzer dürfen die Seiten bearbeiten.', + 'guest_editactive' => 'Jeder darf die Seiten bearbeiten.', + ], 'database' => 'Datenbankeinstellungen', 'dbtype' => 'Datenbank-Typ', 'dbtypeinfo' => 'Mit dieser Einstellung bestimmst du, welche Datenbank die Anwendung für alle Daten nutzen soll.', @@ -155,5 +208,11 @@ 'alert' => 'Gefährlich', 'emergency' => 'Notfall (Sehr ernst)', ], - ] + ], + 'errors' => [ + '401' => [ + 'title' => '401: Nicht authorisiert', + 'message' => 'Du hast nicht die benötigten Rechte, um die angeforderte Aktion auszuführen. Bitte logge dich ein.', + ] + ], ]; diff --git a/resources/lang/en/ui.php b/resources/lang/en/ui.php index 3ec0404..304c1bf 100644 --- a/resources/lang/en/ui.php +++ b/resources/lang/en/ui.php @@ -57,6 +57,37 @@ 'top' => 'Scroll to top', ], 'backend' => [ + 'user' => [ + 'welcome' => 'Welcome back, :user!', + 'login' => 'Login', + 'logout' => 'Logout', + 'register' => 'Registration', + 'no_account' => 'No account? Create one now:', + 'information' => 'User information', + 'name' => 'User name', + 'email' => 'A valid Email address', + 'name_or_mail' => 'Username or Email', + 'password' => 'Enter a password', + 'password_confirm' => 'Confirm your password', + 'token' => 'In order to register, you must provide a unique key, that you should have received.', + 'token_input' => 'Place your key here', + 'register_submit' => 'Finish registration', + 'account' => 'Account', + 'accountinfo' => 'On this page you can change your account + settings like Email-address or refresh your + API token.', + 'api_token' => 'API token', + 'api_token_info' => 'Your current API token is :token. Regenerate it in case you experience problems such as not being able to edit pages via ContentTools.', + 'api_token_regen' => 'Regenerate API token', + 'pw_verify' => 'Please verify the changes with your password.', + ], + 'token' => 'Token', + 'tokeninfo' => 'This site lists all existing tokens for registration of users in limited mode. If you want someone to be able to register, create a token with at least one use and give it to him/her. It will be invalidated automatically upon registration.', + 'token_remaining' => 'Remaining uses', + 'create_token' => 'Create new token', + 'token_amount' => 'Amount to be created', + 'token_uses' => 'Number of uses for each token', + 'tokenplaceholder' => 'Amount', 'backtomain' => 'Back to main page', 'dashboard' => 'Dashboard', 'settings' => 'Settings', @@ -65,8 +96,8 @@ 'pages' => 'Pages in this wiki', 'dbsize' => 'Current database size', 'dbbackup' => 'Backup database', - 'viewcache' => 'Size of cached views', - 'viewflush' => 'Flush views cache', + 'cache' => 'Größe des Caches', + 'cacheflush' => 'Cache leeren', 'exporthtmldesc' => 'Alle Seiten im HTML-Format als ZIP exportieren', 'exporthtmllink' => 'Exportieren …', ], @@ -74,6 +105,8 @@ 'lognone' => 'There are no logfiles present!', 'logs' => 'Application logs', 'availablelogs' => 'Available logfiles', + 'logfull' => 'View complete log', + 'logtail' => 'Only view the end of the log', ], 'settings' => [ 'settings' => 'Settings', @@ -93,6 +126,21 @@ 'en' => 'English (USA)', 'de' => 'German (Germany)', ], + 'auth' => [ + 'title' => 'Users and security', + 'active' => 'Activate authentication', + 'activeinfo' => 'Activating the authentication system requires login depending on settings. Be careful with this setting to not lock yourself out!', + 'isactive' => 'Active', + 'isnotactive' => 'Not active', + 'register' => 'Registering', + 'registerinfo' => 'This setting determines if everyone can just register, or if a new user will need a token from you in order to register.', + 'registeractive' => 'Everyone can register', + 'registerinactive' => 'Registering requires a token', + 'guest_edit' => 'Guest editing', + 'guest_editinfo' => 'This setting controls the editing functions like creating or removing pages. Either all or only logged-in users are allowed to do so.', + 'guest_editinactive'=> 'Only logged-in users are allowed to edit.', + 'guest_editactive' => 'Everybody may edit.', + ], 'database' => 'Database settings', 'dbtype' => 'Database type', 'dbtypeinfo' => 'This determines which type of database will be used to store everything in.', @@ -154,5 +202,11 @@ 'alert' => 'Alert', 'emergency' => 'Emergency (Extremely severe)', ], - ] + ], + 'errors' => [ + '401' => [ + 'title' => '401: Unauthorized', + 'message' => 'You are not allowed to perform the requested action. Please login and try again.', + ] + ], ]; diff --git a/resources/views/admin/account.blade.php b/resources/views/admin/account.blade.php new file mode 100644 index 0000000..2bb049c --- /dev/null +++ b/resources/views/admin/account.blade.php @@ -0,0 +1,54 @@ +@extends('app.backend') + +@section('content') +

      {{ trans('ui.backend.user.account') }}

      +
      + {{ trans('ui.backend.user.accountinfo') }} +
      + + @if(count($errors) > 0) +
      +
        + @foreach($errors->all() as $error) +
      • {{ $error }}
      • + @endforeach +
      +
      + @endif + +
      + {!! csrf_field() !!} + +
      + {{ trans('ui.backend.user.information') }} + + + + + + +
      + +
      + {{ trans('ui.backend.user.api_token') }} + +
      + {{ trans('ui.backend.user.api_token_info', ['token' => \Auth::user()->api_token]) }}
      + {{ trans('ui.backend.user.api_token_regen') }} +
      +
      + +
      + {{ trans('ui.settings.save')}} + + + +
      +
      +@endsection diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 9b18ec2..9b1ef5d 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -4,14 +4,21 @@

      {{ trans('ui.backend.dashboard') }}

      + @if(!Auth::guest()) +
      + {{ trans('ui.backend.user.welcome', ['user' => Auth::user()->name]) }} + {{ trans('ui.backend.user.logout') }} +
      + @endif +

      {{ trans('ui.backend.stats.title', ['page' => env('APP_TITLE', '')]) }}

      {{ trans('ui.backend.stats.exporthtmldesc') }}: {{ trans('ui.backend.stats.exporthtmllink')}} -

      {{ trans('ui.backend.stats.pages') }}: {{ $stats->indexedPages }}

      -

      {{ trans('ui.backend.stats.dbsize') }}: {{ $stats->dbSize }} - {{ trans('ui.backend.stats.dbbackup') }} -

      -

      {{ trans('ui.backend.stats.viewcache') }}: {{ $stats->cacheSize }} {{ trans('ui.backend.stats.viewflush')}}

      +

      {{ trans('ui.backend.stats.pages') }}: {{ $stats->indexedPages }}

      +

      {{ trans('ui.backend.stats.dbsize') }}: {{ $stats->dbSize }} + {{ trans('ui.backend.stats.dbbackup') }} +

      +

      {{ trans('ui.backend.stats.cache') }}: {{ $stats->cacheSize }} {{ trans('ui.backend.stats.cacheflush')}}

      +
      - -@endsection + @endsection diff --git a/resources/views/admin/logs.blade.php b/resources/views/admin/logs.blade.php index 9e2a144..6a5fe62 100644 --- a/resources/views/admin/logs.blade.php +++ b/resources/views/admin/logs.blade.php @@ -1,32 +1,42 @@ @extends('app.backend') @section('content') -
      -

      {{ trans('ui.backend.logs') }}

      -
      - {{ trans('ui.backend.loginfo') }} -
      +

      {{ trans('ui.backend.logs') }} {{ $theFile }}

      +
      + {{ trans('ui.backend.loginfo') }} +
      - @if(count($logfiles) > 0) - - + @if(count($logfiles) > 0) +
      +
      {{ trans('ui.backend.availablelogs') }}
      + @foreach($logfiles as $file) +
      + @if($file == $theFile) + + @endif + {{ $file }} + @if($file == $theFile) + + @endif +
      + @endforeach +
      - - @foreach($lines as $line) + +
      + @foreach($lines as $line) @endforeach -
      {{ $line->number }} {{ $line->contents }}
      - @else -
      -

      {{ trans('ui.backend.lognone') }}

      -
      - @endif -
      + + @else +
      +

      {{ trans('ui.backend.lognone') }}

      +
      + @endif @endsection diff --git a/resources/views/admin/settings.blade.php b/resources/views/admin/settings.blade.php index 44e4fc0..fa6c1b3 100644 --- a/resources/views/admin/settings.blade.php +++ b/resources/views/admin/settings.blade.php @@ -52,6 +52,37 @@ +
      + {{ trans('ui.settings.auth.title') }} + + + + + + + + + +
      +
      {{ trans('ui.settings.database')}} diff --git a/resources/views/admin/token.blade.php b/resources/views/admin/token.blade.php new file mode 100644 index 0000000..11d3d75 --- /dev/null +++ b/resources/views/admin/token.blade.php @@ -0,0 +1,38 @@ +@extends('app.backend') + +@section('content') +

      {{ trans('ui.backend.token') }}

      +
      + {{ trans('ui.backend.tokeninfo') }} +
      + +
      + {!! csrf_field() !!} + @if(count($token) > 0) + + + + + + @foreach($token as $t) + + + + + @endforeach +
      {{ trans('ui.backend.token') }}{{ trans('ui.backend.token_remaining') }}
      {{ $t['token'] }}
      + @else +
      {{ trans('ui.backend.no_token') }}
      + @endif + +
      + {{ trans('ui.backend.create_token') }} + + + + + +
      + +
      +@endsection diff --git a/resources/views/app/backend.blade.php b/resources/views/app/backend.blade.php index 9fe232e..3deb576 100644 --- a/resources/views/app/backend.blade.php +++ b/resources/views/app/backend.blade.php @@ -79,11 +79,6 @@ extraKeys: { "Tab": false } }); } - - /* - * TAB NAVIGATION - */ - // Enable tab navigation if($('#tabs').length) { $('#tabs').tabs(); @@ -170,6 +165,10 @@
    • {{ trans('ui.backend.settings') }}
    • {{ trans('ui.settings.advanced') }}
    • {{ trans('ui.backend.logs') }}
    • +
    • {{ trans('ui.backend.token') }}
    • + @if(Auth::check()) +
    • {{ trans('ui.backend.user.account') }}
    • + @endif diff --git a/resources/views/app/frontend.blade.php b/resources/views/app/frontend.blade.php index 9ccfda3..4451a74 100755 --- a/resources/views/app/frontend.blade.php +++ b/resources/views/app/frontend.blade.php @@ -41,8 +41,13 @@ mediaLibraryGetter: "{{ url('/ajax/getMedia') }}", mediaLibrarySetter: "{{ url('/ajax/uploadMedia') }}", contentToolsGetter: "{{ url('/ajax/getPageContent') }}", - contentToolsSetter: "{{ url('/edit/json') }}", - csrfToken: "{!! csrf_token() !!}" + contentToolsSetter: "{{ url('/api/edit') }}", + csrfToken: "{!! csrf_token() !!}", + @if(Auth::check()) + api_token: "{{ Auth::user()->api_token }}", + @else + api_token: "unauthenticated", + @endif }; // Also we will need some translation strings @@ -195,7 +200,15 @@
    • {{ trans('ui.frontend.trash') }}
    • {{ trans('ui.frontend.index') }}
    • {{ trans('ui.frontend.create') }}
    • -
    • {{ trans('ui.frontend.backend') }}
    • + @if(env('AUTH_ACTIVE', true)) + @if(Auth::check()) +
    • {{ trans('ui.frontend.backend') }}
    • + @else +
    • {{ trans('ui.backend.user.login') }}
    • + @endif + @else +
    • {{ trans('ui.frontend.backend') }}
    • + @endif diff --git a/resources/views/errors/401.blade.php b/resources/views/errors/401.blade.php new file mode 100644 index 0000000..ccca9ac --- /dev/null +++ b/resources/views/errors/401.blade.php @@ -0,0 +1,9 @@ +@extends('app.frontend') + +@section('content') +

      {{ trans('ui.errors.401.title') }}

      +
      + {{ trans('ui.errors.401.message') }}
      + {{ trans('ui.backend.user.login') }} +
      +@endsection diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php new file mode 100644 index 0000000..88c20a9 --- /dev/null +++ b/resources/views/errors/404.blade.php @@ -0,0 +1,9 @@ +@extends('app.frontend') + +@section('content') +

      404: File not found

      +
      + The page you were looking for does not exist!
      + Create this page +
      +@endsection diff --git a/resources/views/page/show.blade.php b/resources/views/page/show.blade.php index 590671b..98d1fcd 100755 --- a/resources/views/page/show.blade.php +++ b/resources/views/page/show.blade.php @@ -2,15 +2,17 @@ @section('content')
      -

      {{ $page->title }} {{ (strlen($page->slug) > 60) ? substr($page->slug, 0, 60) . "…" : $page->slug }}

      - +

      {{ $page->title }} {{ (strlen($page->slug) > 60) ? substr($page->slug, 0, 60) . "…" : $page->slug }}

      + @if(env('AUTH_GUEST_EDIT') || Auth::check()) + + @endif @if(count($referencingPages) > 0)

      {{ trans('ui.page.referencing') }}: diff --git a/resources/views/user/login.blade.php b/resources/views/user/login.blade.php new file mode 100644 index 0000000..b041438 --- /dev/null +++ b/resources/views/user/login.blade.php @@ -0,0 +1,16 @@ +@extends('app.frontend') + +@section('content') +

      {{ trans('ui.backend.user.login') }}

      +
      + {!! csrf_field() !!} +
      + {{ trans('ui.backend.user.login') }} + + + + {{ trans('ui.backend.user.no_account') }} {{ trans('ui.backend.user.register') }} +
      + +
      +@endsection diff --git a/resources/views/user/register.blade.php b/resources/views/user/register.blade.php new file mode 100644 index 0000000..620dd58 --- /dev/null +++ b/resources/views/user/register.blade.php @@ -0,0 +1,32 @@ +@extends('app.frontend') + +@section('content') +

      {{ trans('ui.backend.user.register') }}

      + + @if(count($errors) > 0) +
      +
        + @foreach($errors->all() as $error) +
      • {{ $error }}
      • + @endforeach +
      +
      + @endif + +
      + {!! csrf_field() !!} +
      + {{ trans('ui.backend.user.information') }} + + + + + + @if(!env('AUTH_REGISTER')) + + + @endif +
      + +
      +@endsection