diff --git a/src/client/js/app-edit.js b/src/client/js/app-edit.js index 89bc1cd..54754e6 100644 --- a/src/client/js/app-edit.js +++ b/src/client/js/app-edit.js @@ -16,6 +16,8 @@ if (mode == 'edit') { initEditMode(); + } else if (mode == 'createArticle') { + initCreateArticle(); } else if (mode == 'createUser') { initCreateUserForm(); } @@ -36,6 +38,21 @@ editor.on('scroll', onEditorScroll) } + function initCreateArticle() { + document.getElementById('createArticleBtn').addEventListener('click', function() { + var articleFilename = slimwiki.settings.articleFilename, + pageTitle = slimwiki.settings.pageTitle; + callRpc('editor', 'createArticle', [ articleFilename, pageTitle ], function(result, error) { + if (error) { + console.error('Creating article failed:', error); + showErrorLogged(); + } else { + location.href = slimwiki.settings.requestPath + '?edit'; + } + }) + }); + } + function initCreateUserForm() { document.getElementById('create-user-box').style.display = 'block'; diff --git a/src/client/less/view.less b/src/client/less/view.less index 01c8559..f3b0757 100644 --- a/src/client/less/view.less +++ b/src/client/less/view.less @@ -10,7 +10,7 @@ body { // Views -#fatal-error-message { +#jumbo-message { margin-top: 150px; text-align: center; font-size: 20px; diff --git a/src/index.php b/src/index.php index 4c174a9..74f9db7 100644 --- a/src/index.php +++ b/src/index.php @@ -37,7 +37,7 @@ function init() { require_once __DIR__ . '/server/logic/Main.php'; - Main::get()->dispatch($baseUrl, $basePath, $requestPathArray, $uriParts['query']); + (new Main())->dispatch($baseUrl, $basePath, $requestPathArray, $uriParts['query']); } init(); diff --git a/src/server/i18n/de.php b/src/server/i18n/de.php index 231f2a3..ee66119 100644 --- a/src/server/i18n/de.php +++ b/src/server/i18n/de.php @@ -11,6 +11,9 @@ $i18n = array( 'button.back' => 'Zurück', 'button.edit' => 'Bearbeiten', 'button.createUser' => 'Benutzer erstellen', + 'button.createArticle' => 'Artikel anlegen', + 'createArticle.text' => 'Diesen Artikel gibt es noch nicht.', + 'createArticle.content' => 'Dies ist ihr neuer Artikel.', 'createUser.userName' => 'Benutzername', 'createUser.password' => 'Passwort', 'createUser.showConfig' => 'Konfiguration anzeigen', diff --git a/src/server/i18n/en.php b/src/server/i18n/en.php index 6534774..d15b4b6 100644 --- a/src/server/i18n/en.php +++ b/src/server/i18n/en.php @@ -11,6 +11,9 @@ $i18n = array( 'button.back' => 'Back', 'button.edit' => 'Edit', 'button.createUser' => 'Create user', + 'button.createArticle' => 'Create article', + 'createArticle.text' => 'This article does not exist yet.', + 'createArticle.content' => 'This is your new article.', 'createUser.userName' => 'User name', 'createUser.password' => 'Password', 'createUser.showConfig' => 'Show config', diff --git a/src/server/layout/page.php b/src/server/layout/page.php index d0ac06a..7b86ded 100644 --- a/src/server/layout/page.php +++ b/src/server/layout/page.php @@ -45,7 +45,9 @@ $mode = $data['mode']; $settings = array( "mode" => $mode ); - if ($mode == 'edit') { + if ($mode == 'edit' || $mode == 'createArticle') { + $settings['pageTitle'] = end($data['breadcrumbs'])['name']; + $settings['requestPath'] = $data['requestPath']; $settings['articleFilename'] = $data['articleFilename']; } echo json_encode($settings); @@ -61,14 +63,14 @@ $mode = $data['mode']; if ($mode != 'view') { // Show an error message if JavaScript is off or if the browser is not supported. // NOTE: In view mode we don't show an error. Instead, syntax highlighting will be off for unsupported browsers. - ?>
+ ?>
diff --git a/src/server/logic/Context.php b/src/server/logic/Context.php index 8963681..e22f1cd 100644 --- a/src/server/logic/Context.php +++ b/src/server/logic/Context.php @@ -75,7 +75,7 @@ class Context { public function getRenderService() { if (is_null($this->renderService)) { require_once __DIR__ . '/RenderService.php'; - $this->renderService = new RenderService(); + $this->renderService = new RenderService($this); } return $this->renderService; } diff --git a/src/server/logic/EditorService.php b/src/server/logic/EditorService.php index 2a41bfe..e7b4db6 100644 --- a/src/server/logic/EditorService.php +++ b/src/server/logic/EditorService.php @@ -10,7 +10,7 @@ class EditorService { } public function isRpcMethod($methodName) { - return ($methodName == 'saveArticle' || $methodName == 'createUserConfig'); + return ($methodName == 'createArticle' || $methodName == 'saveArticle' || $methodName == 'createUserConfig'); } // Returns one of: 'logged-in', 'no-credentials', 'wrong-credentials' @@ -62,6 +62,17 @@ class EditorService { return null; } + public function createArticle($articleFilename, $pageTitle) { + if (! $this->str_endswith($articleFilename, '.md')) { + $articleFilename .= '.md'; + } + + $markdownText = $pageTitle . "\n" . str_repeat('=', strlen($pageTitle)) . "\n\n" + . $this->context->getI18n()['createArticle.content']; + + return $this->saveArticle($articleFilename, $markdownText); + } + public function saveArticle($articleFilename, $markdownText) { $this->assertLoggedIn(); @@ -111,4 +122,11 @@ class EditorService { return "\$config['user.".strtolower($user)."'] = array('type' => '$type', 'salt' => '$salt', 'hash' => '$hash');"; } + function str_endswith($string, $test) { + $strlen = strlen($string); + $testlen = strlen($test); + if ($testlen > $strlen) return false; + return substr_compare($string, $test, $strlen - $testlen, $testlen) === 0; + } + } diff --git a/src/server/logic/Main.php b/src/server/logic/Main.php index 3e51764..6cf84cc 100644 --- a/src/server/logic/Main.php +++ b/src/server/logic/Main.php @@ -4,22 +4,13 @@ require_once __DIR__ . '/Context.php'; class Main { - private static $singleton; - private $context; - private function __construct() { + public function __construct() { $this->context = new Context(); } - public static function get() { - if (is_null(self::$singleton)) { - self::$singleton = new self(); - } - return self::$singleton; - } - // Parameters: // - $baseUrl: E.g. 'http://localhost/slim-wiki/?edit' // - $basePath: E.g. '/slim-wiki/' @@ -96,18 +87,28 @@ class Main { $articleFilename = $this->getArticleFilename($requestPathArray); if ($articleFilename == null) { - header('HTTP/1.0 404 Not Found'); + header('HTTP/1.0 403 Forbidden'); header('Content-Type:text/html; charset=utf-8'); - echo '

File not found

'; // TODO: Show error page + echo '

Forbidden

'; } else { $config = $this->context->getConfig(); + $renderService = $this->context->getRenderService(); + $fatalErrorMessage = null; - if ($mode == 'edit') { - $fatalErrorMessage = $this->context->getEditorService()->checkForError($articleFilename); + if ($mode == 'view') { + if (! $renderService->articleExists($articleFilename)) { + $mode = 'noSuchArticle'; + } + } else if ($mode == 'edit') { + $editorService = $this->context->getEditorService(); + $fatalErrorMessage = $editorService->checkForError($articleFilename); if ($fatalErrorMessage != null) { $fatalErrorMessage = $this->context->getI18n()['error.editingArticleFailed'] . '
' . $fatalErrorMessage; $mode = 'error'; + } else if (! $renderService->articleExists($articleFilename)) { + $mode = 'createArticle'; + $showCreateUserButton = false; } } @@ -121,14 +122,17 @@ class Main { $data[$key] = $config[$key]; } - $data['breadcrumbs'] = $this->createBreadcrumbs($requestPathArray, $config['wikiName']); + $data['breadcrumbs'] = $this->createBreadcrumbs($requestPathArray); $data['showCreateUserButton'] = $showCreateUserButton; $data['requestPath'] = implode('/', $requestPathArray); $data['articleFilename'] = $articleFilename; - $articleMarkdown = file_get_contents($this->context->getArticleBaseDir() . $articleFilename); - $data['articleMarkdown'] = $articleMarkdown; - $data['articleHtml'] = $this->context->getRenderService()->renderMarkdown($articleMarkdown, $mode == 'edit'); + + if ($mode == 'view' || $mode == 'edit') { + $articleMarkdown = file_get_contents($this->context->getArticleBaseDir() . $articleFilename); + $data['articleMarkdown'] = $articleMarkdown; + $data['articleHtml'] = $renderService->renderMarkdown($articleMarkdown, $mode == 'edit'); + } $this->renderPage($data); } @@ -162,14 +166,13 @@ class Main { if (! $this->context->isValidArticleFilename($articleFilename)) { // Attempt to break out of article base directory (e.g. `../../outside.ext`) return null; - } else if (file_exists($articleFullFilename) && is_readable($articleFullFilename)) { - return $articleFilename; } else { - return null; + return $articleFilename; } } - private function createBreadcrumbs($requestPathArray, $wikiName) { + private function createBreadcrumbs($requestPathArray) { + $wikiName = $this->context->getConfig()['wikiName']; $pathCount = count($requestPathArray); $breadcrumbArray = array(array('name' => $wikiName, 'path' => '', 'active' => ($pathCount == 0))); diff --git a/src/server/logic/RenderService.php b/src/server/logic/RenderService.php index 59485e2..2edf901 100644 --- a/src/server/logic/RenderService.php +++ b/src/server/logic/RenderService.php @@ -2,6 +2,17 @@ class RenderService { + private $context; + + + public function __construct($context) { + $this->context = $context; + } + + public function articleExists($articleFilename) { + return file_exists($this->context->getArticleBaseDir() . $articleFilename); + } + public function renderMarkdown($markdownText, $isEditMode) { require_once __DIR__ . '/../lib/parsedown/Parsedown.php'; $html = Parsedown::instance()->text($markdownText);