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);
+ }
+
+}