mirror of
https://github.com/til-schneider/slim-wiki.git
synced 2025-08-06 08:37:31 +02:00
Automatically updating preview while editing
This commit is contained in:
@@ -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);
|
||||
|
@@ -90,7 +90,7 @@ html, body {
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
#content {
|
||||
padding: 30px 0 150px;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.content {
|
||||
.markdown {
|
||||
font-size: 16px;
|
||||
color: @colorText;
|
||||
line-height: 1.4;
|
||||
|
@@ -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);
|
||||
|
@@ -51,7 +51,7 @@
|
||||
$isFirst = false;
|
||||
}
|
||||
?></div></nav>
|
||||
<article class="content main-column"><?php echo $data['articleHtml']; ?></article>
|
||||
<article id="content" class="markdown main-column"><?php echo $data['articleHtml']; ?></article>
|
||||
<?php
|
||||
if (isset($data['footerHtml'])) {
|
||||
?><footer><div class="main-column"><?php echo $data['footerHtml']; ?></div></footer><?php
|
||||
|
@@ -1,9 +1,23 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../lib/parsedown/Parsedown.php';
|
||||
|
||||
class Main {
|
||||
|
||||
private static $singleton;
|
||||
|
||||
private $articleBaseDir;
|
||||
|
||||
|
||||
private function __construct() {
|
||||
$this->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),
|
||||
|
24
src/server/logic/RenderService.php
Normal file
24
src/server/logic/RenderService.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class RenderService {
|
||||
|
||||
private static $singleton;
|
||||
|
||||
|
||||
public static function get() {
|
||||
if (is_null(self::$singleton)) {
|
||||
self::$singleton = new self();
|
||||
}
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
public function isRpcMethod($methodName) {
|
||||
return ($methodName == 'renderMarkdown');
|
||||
}
|
||||
|
||||
public function renderMarkdown($markdownText) {
|
||||
require_once __DIR__ . '/../lib/parsedown/Parsedown.php';
|
||||
return Parsedown::instance()->text($markdownText);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user