From 0b36d30e619350026f86ef1cb80aa34907140e73 Mon Sep 17 00:00:00 2001 From: til-schneider Date: Tue, 22 Dec 2015 10:56:59 +0100 Subject: [PATCH] Automatically updating preview while editing --- src/client/js/app-edit.js | 83 +++++++++++++++++++++++++++--- src/client/less/layout.less | 2 +- src/client/less/markdown.less | 2 +- src/index.php | 4 +- src/server/layout/page.php | 2 +- src/server/logic/Main.php | 83 ++++++++++++++++++++++++++---- src/server/logic/RenderService.php | 24 +++++++++ 7 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 src/server/logic/RenderService.php diff --git a/src/client/js/app-edit.js b/src/client/js/app-edit.js index 9132153..e64d09b 100644 --- a/src/client/js/app-edit.js +++ b/src/client/js/app-edit.js @@ -1,10 +1,79 @@ (function(CodeMirror) { - var editor = CodeMirror.fromTextArea(document.getElementById('editor'), { - // Config see: https://codemirror.net/doc/manual.html - mode: 'gfm', - lineNumbers: false, - lineWrapping: true, - theme: 'railscasts' - }); + + var editor, + updatePreviewDelay = 1000, + updatePreviewTimeout = null, + updatePreviewRunning = false, + previewIsDirty = false; + + + function init() { + editor = CodeMirror.fromTextArea(document.getElementById('editor'), { + // Config see: https://codemirror.net/doc/manual.html + mode: 'gfm', + lineNumbers: false, + lineWrapping: true, + theme: 'railscasts' + }); + + editor.on('changes', onEditorChange); + } + + function onEditorChange() { + previewIsDirty = true; + if (! updatePreviewRunning) { + window.clearTimeout(updatePreviewTimeout); + updatePreviewTimeout = window.setTimeout(function() { + previewIsDirty = false; + + updatePreviewRunning = true; + var start = new Date().getTime(); + callRpc('render', 'renderMarkdown', [ editor.getValue() ], function(result, error) { + updatePreviewRunning = false; + + if (error) { + console.error('Rendering markdown failed', error); + } else { + document.getElementById('content').innerHTML = result; + console.log('Updated preview in ' + (new Date().getTime() - start) + ' ms'); + } + + if (previewIsDirty) { + onEditorChange(); + } + }) + }, updatePreviewDelay); + } + } + + function callRpc(objectName, methodName, paramArray, done) { + var request = new XMLHttpRequest(), + requestJson; + + request.open('POST', 'rpc/' + objectName, true); + request.onreadystatechange = function () { + if (request.readyState == 4) { + if (request.status != 200) { + done(null, 'Request failed with status ' + request.status); + } else { + try { + var responseJson = JSON.parse(request.responseText); + if (responseJson.error) { + done(null, 'Request failed on server-side: ' + responseJson.error.message); + } else { + done(responseJson.result); + } + } catch (err) { + done(null, 'Request failed: ' + err); + } + } + } + }; + + requestJson = { jsonrpc: '2.0', method: methodName, params: paramArray || [], id: 1 }; + request.send(JSON.stringify(requestJson)); + } + + init(); })(CodeMirror); diff --git a/src/client/less/layout.less b/src/client/less/layout.less index 168844e..f908c05 100644 --- a/src/client/less/layout.less +++ b/src/client/less/layout.less @@ -90,7 +90,7 @@ html, body { } } -.content { +#content { padding: 30px 0 150px; } diff --git a/src/client/less/markdown.less b/src/client/less/markdown.less index 7bfb320..8fab81b 100644 --- a/src/client/less/markdown.less +++ b/src/client/less/markdown.less @@ -1,4 +1,4 @@ -.content { +.markdown { font-size: 16px; color: @colorText; line-height: 1.4; diff --git a/src/index.php b/src/index.php index c40135e..3e8bd4a 100644 --- a/src/index.php +++ b/src/index.php @@ -35,6 +35,6 @@ $baseUrl = 'http' . ($https ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . $baseP unset($uriPathArray, $scriptPathArray, $basePathArray, $isBasePath, $https); -require_once __DIR__ . '/server/logic/main.php'; +require_once __DIR__ . '/server/logic/Main.php'; -(new Main())->dispatch($baseUrl, $basePath, $requestPathArray); +Main::get()->dispatch($baseUrl, $basePath, $requestPathArray); diff --git a/src/server/layout/page.php b/src/server/layout/page.php index e045f65..89c0c6f 100644 --- a/src/server/layout/page.php +++ b/src/server/layout/page.php @@ -51,7 +51,7 @@ $isFirst = false; } ?> -
+
articleBaseDir = realpath(__DIR__ . '/../../articles') . '/'; + } + + public static function get() { + if (is_null(self::$singleton)) { + self::$singleton = new self(); + } + return self::$singleton; + } + // Parameters: // - $baseUrl: E.g. 'http://localhost/slim-wiki/' // - $basePath: E.g. '/slim-wiki/' @@ -11,13 +25,60 @@ class Main { public function dispatch($baseUrl, $basePath, $requestPathArray) { $config = $this->loadConfig(); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost($requestPathArray); + } else { + $this->handleGet($baseUrl, $basePath, $requestPathArray, $config); + } + } + + private function handlePost($requestPathArray) { + if (count($requestPathArray) == 2 && $requestPathArray[0] == 'rpc') { + $requestData = json_decode(file_get_contents('php://input'), true); + + $objectName = $requestPathArray[1]; + $object = null; + if ($objectName == 'render') { + require_once __DIR__ . '/RenderService.php'; + $object = RenderService::get(); + } + + $responseData = array( + 'jsonrpc' => '2.0', + 'id' => $requestData['id'] + ); + if ($object == null) { + $responseData['error'] = array( 'code' => -32601, 'message' => "Object not found: $objectName" ); + } else { + $methodName = $requestData['method']; + + if (! $object->isRpcMethod($methodName)) { + $responseData['error'] = array( 'code' => -32601, 'message' => "Method not found or not public: $objectName.$methodName" ); + } else { + try { + $responseData['result'] = call_user_func_array(array($object, $methodName), $requestData['params']); + } catch (Exception $exc) { + $msg = "Calling RPC $objectName.$methodName failed"; + error_log($msg . ': ' . $exc->getMessage()); + $responseData['error'] = array( 'code' => -32000, 'message' => $msg ); + } + } + } + + header('Content-Type: application/json'); + echo json_encode($responseData); + } else { + header('HTTP/1.0 404 Not Found'); + } + } + + private function handleGet($baseUrl, $basePath, $requestPathArray, $config) { $isEditMode = isset($requestPathArray[0]) && $requestPathArray[0] == 'edit'; if ($isEditMode) { array_shift($requestPathArray); } - $articleBaseDir = realpath(__DIR__ . '/../../articles') . '/'; - $articleFilename = $this->getArticleFilename($articleBaseDir, $requestPathArray); + $articleFilename = $this->getArticleFilename($requestPathArray); if ($articleFilename == null) { header('HTTP/1.0 404 Not Found'); header('Content-Type:text/html; charset=utf-8'); @@ -32,18 +93,20 @@ class Main { $data[$key] = $config[$key]; } - $data['breadcrumbs'] = $this->createBreadcrumbs($articleBaseDir, $requestPathArray, $config['wikiName']); + $data['breadcrumbs'] = $this->createBreadcrumbs($requestPathArray, $config['wikiName']); $articleMarkdown = file_get_contents($articleFilename); $data['articleMarkdown'] = $articleMarkdown; - $data['articleHtml'] = Parsedown::instance()->text($articleMarkdown); + + require_once __DIR__ . '/RenderService.php'; + $data['articleHtml'] = RenderService::get()->renderMarkdown($articleMarkdown); $this->renderPage($data); } } - private function getArticleFilename($articleBaseDir, $requestPathArray) { - $articleFilename = $articleBaseDir . implode('/', $requestPathArray); + private function getArticleFilename($requestPathArray) { + $articleFilename = $this->articleBaseDir . implode('/', $requestPathArray); // Support `index.md` for directories if (is_dir($articleFilename)) { @@ -78,7 +141,7 @@ class Main { return $config; } - private function createBreadcrumbs($articleBaseDir, $requestPathArray, $wikiName) { + private function createBreadcrumbs($requestPathArray, $wikiName) { $pathCount = count($requestPathArray); $breadcrumbArray = array(array('name' => $wikiName, 'path' => '', 'active' => ($pathCount == 0))); @@ -88,7 +151,7 @@ class Main { $currentPath .= ($i == 0 ? '' : '/') . $pathPart; $isLast = ($i == $pathCount - 1); - if ($isLast || file_exists($articleBaseDir . $currentPath . '/index.md')) { + if ($isLast || file_exists($this->articleBaseDir . $currentPath . '/index.md')) { // This is the requested file or an directory having an index -> Add it $breadcrumbArray[] = array( 'name' => str_replace('_', ' ', $pathPart), diff --git a/src/server/logic/RenderService.php b/src/server/logic/RenderService.php new file mode 100644 index 0000000..1d2a4e8 --- /dev/null +++ b/src/server/logic/RenderService.php @@ -0,0 +1,24 @@ +text($markdownText); + } + +}