mirror of
https://github.com/typemill/typemill.git
synced 2025-01-17 05:18:19 +01:00
Version 1.2.1 Editor Improvement
This commit is contained in:
parent
0df0bd5a8e
commit
1aa20023d0
2
cache/lastCache.txt
vendored
2
cache/lastCache.txt
vendored
@ -1 +1 @@
|
||||
1530040430
|
||||
1530865190
|
@ -1,4 +1,4 @@
|
||||
# Features
|
||||
# Features qwer
|
||||
|
||||
TYPEMILL has a limited set of features right now. It transforms a bunch of **markdown files** into a **website** and generates a list of contents for **navigation**.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# About TYPEMILL
|
||||
# Typemill
|
||||
|
||||
TYPEMILL is a simple flat file CMS to create a website like this. It transforms a bunch of **text files** (Markdown) into a **website** and generates a **navigation**.
|
||||
TYPEMILL is a simple flat file CMS to create a website like this. It transforms a bunch of **text files** (Markdown) into a **website** and generates a **navigation**.
|
||||
|
||||
TYPEMILL is under construction. Right now it provides only a very basic editor and a simple admin area for settings, plugins and themes. The author-experience will be improved step by step and output formats for e-books like mobi and epub are planned for the future.
|
||||
|
||||
|
@ -51,6 +51,20 @@ A paragraph is a simple text-block separated with a new line above and below.
|
||||
|
||||
A paragraph is a simple text-block separated with a new line above and below.
|
||||
|
||||
## Soft Linebreak
|
||||
|
||||
````
|
||||
For a soft linebreak (eg. for dialoges in literature), add two spaces at the end of a line and use a simple return.
|
||||
|
||||
She said: "Hello"
|
||||
He said: "again"
|
||||
````
|
||||
|
||||
For a soft linebreak (eg. for dialoges in literature), add two spaces at the end of a line and use a simple return.
|
||||
|
||||
She said: "Hello"
|
||||
He said: "again"
|
||||
|
||||
##Emphasis
|
||||
|
||||
````
|
||||
@ -137,11 +151,11 @@ Or you can use a shortcut like http://typemill.net.
|
||||
```
|
||||
The same rules as with links, but with a !
|
||||
|
||||
![alt-text](/info/markdown.png)
|
||||
![alt-text](/media/markdown.png)
|
||||
|
||||
![alt-text](/info/markdown.png "my title")
|
||||
![alt-text](/media/markdown.png "my title")
|
||||
|
||||
![alt-text](/info/markdown.png "my title"){#myid .myclass}
|
||||
![alt-text](/media/markdown.png "my title"){#myid .myclass}
|
||||
```
|
||||
|
||||
The same rules as with links, but with a !
|
||||
@ -152,6 +166,40 @@ The same rules as with links, but with a !
|
||||
|
||||
![alt-text](/media/markdown.png "my title"){#myid .imgClass .myClass}
|
||||
|
||||
## Linked Images
|
||||
|
||||
````
|
||||
You can link an image with a nested syntax like this:
|
||||
|
||||
[![alt-text](/media/markdown.png)](https://typemill.net)
|
||||
````
|
||||
|
||||
You can link an image with a nested syntax like this:
|
||||
|
||||
[![alt-text](/media/markdown.png)](https://typemill.net)
|
||||
|
||||
## Image Position
|
||||
|
||||
````
|
||||
You can controll the image position with the classes .left, .right and .middle like this:
|
||||
|
||||
![alt-text](/media/markdown.png){.left}
|
||||
![alt-text](/media/markdown.png){.right}
|
||||
![alt-text](/media/markdown.png){.middle}
|
||||
````
|
||||
|
||||
![image float left](/media/markdown.png){.left}
|
||||
|
||||
The first image should float on the left side of this paragraph. This might not work with all themes. If you are a theme developer, please ensure that you support the image classes "left", "right" and "middle".
|
||||
|
||||
![image float right](/media/markdown.png){.right}
|
||||
|
||||
The second image should float on the right side of this paragraph. This might not work with all themes. If you are a theme developer, please ensure that you support the image classes "left", "right" and "middle".
|
||||
|
||||
![image middle](/media/markdown.png){.middle}
|
||||
|
||||
The thirds image should be placed above this paragraph and centered to the middle of the content area. This might not work with all themes. If you are a theme developer, please ensure that you support the image classes "left", "right" and "middle".
|
||||
|
||||
## Blockquote
|
||||
|
||||
```
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Typemill
|
||||
|
||||
TYPEMILL is a small flat file cms designed for **writers**. It creates websites based on markdown files and is a perfect solution for text-works like studies, manuals or documentations. TYPEMILL is simple, lightweight and open source. Just download and start.
|
||||
TYPEMILL is a small flat file cms designed for **writers**. It creates websites based on markdown files and is a perfect solution for text-works like studies, manuals or documentations. TYPEMILL is simple, lightweight and open source. Just download and start.
|
29
readme.md
29
readme.md
@ -15,7 +15,7 @@ TYPEMILL is a small flat file cms designed for writers. It creates websites base
|
||||
* Supports configurable themes and plugins.
|
||||
* Provides an author panel to configure the system, the themes and the plugins.
|
||||
* Creates and manages users.
|
||||
* Online editing is on its way (for time beeing upload markdown files).
|
||||
* Provides a basic online editing (only for existing files so far, in development).
|
||||
* Markdown supports table of contents (TOC), tables, footnotes, abbreviations and definition lists.
|
||||
* Supports MathJax and KaTeX (plugin).
|
||||
* Supports code highlighting (plugin).
|
||||
@ -24,7 +24,9 @@ TYPEMILL is a small flat file cms designed for writers. It creates websites base
|
||||
|
||||
## Installation
|
||||
|
||||
Download TYPEMILL from the [TYPEMILL website](http://typemill.net) or clone this repository with git. Open your git command line (e.g. gitbash), go to your project folder (e.g. htdocs) and type:
|
||||
Download TYPEMILL from the [TYPEMILL website](http://typemill.net), unzip the files and you are done.
|
||||
|
||||
If you are a developer, you can also clone this repository. To do so, open your git command line (e.g. gitbash), go to your project folder (e.g. htdocs) and type:
|
||||
|
||||
git clone git://github.com/trendschau/typemill.git
|
||||
|
||||
@ -33,7 +35,7 @@ The GitHub-version has no vendor-folder, so you have to update and include all l
|
||||
composer update
|
||||
If you did not use composer before, please go to the [composer website](http://getcomposer.org) and start to learn.
|
||||
|
||||
To run TYPEMILL **live**, simply upload the files to your server.
|
||||
To run TYPEMILL on a **live** system, simply upload the files to your server.
|
||||
|
||||
## Setup
|
||||
|
||||
@ -41,7 +43,7 @@ Please go to `your-typemill-website.com/setup`, create an initial user and then
|
||||
|
||||
## Login
|
||||
|
||||
You can find your login screen under `/tm-author/login` or simply go to `/setup` and you will be redirected to the login-page.
|
||||
You can find your login screen under `/tm/login` or simply go to `/setup` and you will be redirected to the login-page.
|
||||
|
||||
## Requirements
|
||||
|
||||
@ -53,10 +55,25 @@ You can read the full documentation for writers, for theme developers and for pl
|
||||
|
||||
## Contribute
|
||||
|
||||
If you want to contribute to TYPEMILL, please fork this GitHub repository first. Then make your changes and create a pull request. I will review all request as soon as possible.
|
||||
Typemill is still in an early stage and contributions are highly welcome. Here are some ideas for non-coder:
|
||||
|
||||
* Find bugs and errors (open a new issue on github for it).
|
||||
* Improve the documentation.
|
||||
* Describe some missing features and explain, why they are important for other users.
|
||||
|
||||
Some ideas for devs (please fork this repository make your changes and create a pull request):
|
||||
|
||||
* Fix a bug.
|
||||
* Create a nice theme.
|
||||
* Create a new plugin.
|
||||
* Improve the CSS-code with BEM and make it modular.
|
||||
* Rebuild the theme with css-grid.
|
||||
* Improve accessibility of html and css.
|
||||
* Help to establish autotests with selenium or cypress.
|
||||
* Write unit-tests.
|
||||
|
||||
For hints, questions, problems and support, please open up a new issue on GitHub.
|
||||
|
||||
## Licence
|
||||
|
||||
TYPEMILL is published under MIT licence.
|
||||
TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too.
|
@ -29,7 +29,6 @@ class ContentController extends Controller
|
||||
|
||||
public function showContent(Request $request, Response $response, $args)
|
||||
{
|
||||
|
||||
$settings = $this->c->get('settings');
|
||||
$pathToContent = $settings['rootPath'] . $settings['contentFolder'];
|
||||
$uri = $request->getUri();
|
||||
@ -115,8 +114,6 @@ class ContentController extends Controller
|
||||
return $this->render($response, 'content/content.twig', array('navigation' => $structure, 'title' => $title, 'content' => $content, 'item' => $item, 'settings' => $settings ));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function updateArticle(Request $request, Response $response, $args)
|
||||
{
|
||||
/* Extract the parameters from get-call */
|
||||
@ -147,7 +144,7 @@ class ContentController extends Controller
|
||||
$structure = $this->getFreshStructure($pathToContent, $write, $uri);
|
||||
if(!$structure)
|
||||
{
|
||||
return $response->withJson(['errors' => ['content folder is empty']], 404);
|
||||
return $response->withJson(['errors' => ['message' => 'content folder is empty']], 404);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,10 +160,10 @@ class ContentController extends Controller
|
||||
/* search for the url in the structure */
|
||||
$item = Folder::getItemForUrl($structure, $params['url']);
|
||||
}
|
||||
|
||||
|
||||
if(!$item)
|
||||
{
|
||||
return $response->withJson(['errors' => ['requested page-url not found']], 404);
|
||||
return $response->withJson(['errors' => ['message' => 'requested page-url not found']], 404);
|
||||
}
|
||||
|
||||
if($item->elementType == 'folder')
|
||||
@ -182,14 +179,20 @@ class ContentController extends Controller
|
||||
$mdFile = $write->getFile($settings['contentFolder'], $path);
|
||||
if($mdFile)
|
||||
{
|
||||
/* merge title with content for complete markdown document */
|
||||
/* merge title with content forcomplete markdown document */
|
||||
$updatedContent = '# ' . $params['title'] . "\r\n\r\n" . $params['content'];
|
||||
|
||||
/* update the file */
|
||||
$write->writeFile($settings['contentFolder'], $path, $updatedContent);
|
||||
return $response->withJson(['success'], 200);
|
||||
if($write->writeFile($settings['contentFolder'], $path, $updatedContent))
|
||||
{
|
||||
return $response->withJson(['success'], 200);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if file is writable']], 404);
|
||||
}
|
||||
}
|
||||
return $response->withJson(['errors' => ['requested markdown-file not found']], 404);
|
||||
return $response->withJson(['errors' => ['message' => 'requested markdown-file not found']], 404);
|
||||
}
|
||||
|
||||
protected function getFreshStructure($pathToContent, $cache, $uri)
|
||||
|
@ -119,15 +119,17 @@ class PageController extends Controller
|
||||
}
|
||||
|
||||
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
|
||||
|
||||
|
||||
/* initialize parsedown */
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
/* parse markdown-file to content-array */
|
||||
$contentArray = $parsedown->text($contentMD);
|
||||
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
|
||||
|
||||
|
||||
/* get the first image from content array */
|
||||
$firstImage = $this->getFirstImage($contentArray);
|
||||
|
||||
|
@ -40,11 +40,11 @@ class SettingsController extends Controller
|
||||
{
|
||||
/* make sure only allowed fields are stored */
|
||||
$newSettings = array(
|
||||
'title' => $newSettings['title'],
|
||||
'author' => $newSettings['author'],
|
||||
'copyright' => $newSettings['copyright'],
|
||||
'year' => $newSettings['year'],
|
||||
'statpage' => isset($newSettings['startpage']) ? true : false
|
||||
'title' => $newSettings['title'],
|
||||
'author' => $newSettings['author'],
|
||||
'copyright' => $newSettings['copyright'],
|
||||
'year' => $newSettings['year'],
|
||||
'startpage' => isset($newSettings['startpage']) ? true : false
|
||||
);
|
||||
|
||||
$copyright = $this->getCopyright();
|
||||
|
@ -18,34 +18,25 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
array_unshift($this->BlockTypes['['], 'TableOfContents');
|
||||
}
|
||||
|
||||
function text($text)
|
||||
{
|
||||
# make sure no definitions are set
|
||||
$this->DefinitionData = array();
|
||||
|
||||
# standardize line breaks
|
||||
$text = str_replace(array("\r\n", "\r"), "\n", $text);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$text = trim($text, "\n");
|
||||
|
||||
# split text into lines
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
# iterate through lines to identify blocks and return array of content
|
||||
$blocks = $this->getContentArray($lines);
|
||||
function text($text)
|
||||
{
|
||||
$Elements = $this->textElements($text);
|
||||
|
||||
return $blocks;
|
||||
return $Elements;
|
||||
}
|
||||
|
||||
function markup($blocks)
|
||||
{
|
||||
# iterate through array of content and get markup
|
||||
$markup = $this->getMarkup($blocks);
|
||||
|
||||
function markup($Elements)
|
||||
{
|
||||
# convert to markup
|
||||
$markup = $this->elements($Elements);
|
||||
|
||||
# trim line breaks
|
||||
$markup = trim($markup, "\n");
|
||||
|
||||
# merge consecutive dl elements
|
||||
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
|
||||
|
||||
# create table of contents
|
||||
if(isset($this->DefinitionData['TableOfContents']))
|
||||
{
|
||||
$TOC = $this->buildTOC($this->headlines);
|
||||
@ -53,9 +44,6 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
$markup = preg_replace('%(<p[^>]*>\[TOC\]</p>)%i', $TOC, $markup);
|
||||
}
|
||||
|
||||
# merge consecutive dl elements
|
||||
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
|
||||
|
||||
# add footnotes
|
||||
if (isset($this->DefinitionData['Footnote']))
|
||||
{
|
||||
@ -63,10 +51,10 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
|
||||
$markup .= "\n" . $this->element($Element);
|
||||
}
|
||||
|
||||
return $markup;
|
||||
}
|
||||
|
||||
return $markup;
|
||||
}
|
||||
|
||||
# TableOfContents
|
||||
|
||||
protected function blockTableOfContents($line, $block)
|
||||
@ -157,181 +145,8 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
return $markup;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Blocks
|
||||
#
|
||||
|
||||
protected function getContentArray(array $lines)
|
||||
{
|
||||
$CurrentBlock = null;
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
if (chop($line) === '')
|
||||
{
|
||||
if (isset($CurrentBlock))
|
||||
{
|
||||
$CurrentBlock['interrupted'] = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($line, "\t") !== false)
|
||||
{
|
||||
$parts = explode("\t", $line);
|
||||
|
||||
$line = $parts[0];
|
||||
|
||||
unset($parts[0]);
|
||||
|
||||
foreach ($parts as $part)
|
||||
{
|
||||
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
|
||||
|
||||
$line .= str_repeat(' ', $shortage);
|
||||
$line .= $part;
|
||||
}
|
||||
}
|
||||
|
||||
$indent = 0;
|
||||
|
||||
while (isset($line[$indent]) and $line[$indent] === ' ')
|
||||
{
|
||||
$indent ++;
|
||||
}
|
||||
|
||||
$text = $indent > 0 ? substr($line, $indent) : $line;
|
||||
|
||||
# ~
|
||||
|
||||
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
|
||||
|
||||
# ~
|
||||
|
||||
if (isset($CurrentBlock['continuable']))
|
||||
{
|
||||
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
|
||||
|
||||
if (isset($Block))
|
||||
{
|
||||
$CurrentBlock = $Block;
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->isBlockCompletable($CurrentBlock['type']))
|
||||
{
|
||||
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
$marker = $text[0];
|
||||
|
||||
# ~
|
||||
|
||||
$blockTypes = $this->unmarkedBlockTypes;
|
||||
|
||||
if (isset($this->BlockTypes[$marker]))
|
||||
{
|
||||
foreach ($this->BlockTypes[$marker] as $blockType)
|
||||
{
|
||||
$blockTypes []= $blockType;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# ~
|
||||
|
||||
foreach ($blockTypes as $blockType)
|
||||
{
|
||||
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
|
||||
|
||||
if (isset($Block))
|
||||
{
|
||||
$Block['type'] = $blockType;
|
||||
|
||||
if ( ! isset($Block['identified']))
|
||||
{
|
||||
$Blocks []= $CurrentBlock;
|
||||
|
||||
$Block['identified'] = true;
|
||||
}
|
||||
|
||||
if ($this->isBlockContinuable($blockType))
|
||||
{
|
||||
$Block['continuable'] = true;
|
||||
}
|
||||
|
||||
$CurrentBlock = $Block;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
|
||||
{
|
||||
$CurrentBlock['element']['text'] .= "\n".$text;
|
||||
}
|
||||
else
|
||||
{
|
||||
$Blocks []= $CurrentBlock;
|
||||
|
||||
$CurrentBlock = $this->paragraph($Line);
|
||||
|
||||
$CurrentBlock['identified'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
|
||||
{
|
||||
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
$Blocks []= $CurrentBlock;
|
||||
|
||||
unset($Blocks[0]);
|
||||
|
||||
# ~
|
||||
return $Blocks;
|
||||
}
|
||||
|
||||
public function getMarkup($Blocks)
|
||||
{
|
||||
$markup = '';
|
||||
|
||||
foreach ($Blocks as $Block)
|
||||
{
|
||||
if (isset($Block['hidden']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$markup .= "\n";
|
||||
$markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
|
||||
}
|
||||
|
||||
$markup .= "\n";
|
||||
|
||||
# ~
|
||||
|
||||
return $markup;
|
||||
}
|
||||
|
||||
# math support. Check https://github.com/aidantwoods/parsedown/blob/mathjaxlatex/ParsedownExtensionMathJaxLaTeX.php
|
||||
|
||||
|
||||
protected function inlineCode($Excerpt)
|
||||
{
|
||||
$marker = $Excerpt['text'][0];
|
||||
@ -401,18 +216,7 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
/*
|
||||
protected function blockFencedCodeComplete($Block)
|
||||
{
|
||||
$text = $Block['element']['element']['text'];
|
||||
unset($Block['element']['element']['text']);
|
||||
|
||||
$Block['element']['element']['rawHtml'] = "<p>$text</p>";
|
||||
$Block['element']['element']['allowRawHtmlInSafeMode'] = true;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
*/
|
||||
#
|
||||
# Fenced MathJax
|
||||
protected function blockFencedMathJaxLaTeX($Line)
|
||||
@ -436,7 +240,8 @@ class ParsedownExtension extends \ParsedownExtra
|
||||
|
||||
protected function blockFencedMathJaxLaTeXContinue($Line, $Block)
|
||||
{
|
||||
if (isset($Block['complete']))
|
||||
|
||||
if (isset($Block['complete']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class RedirectIfAuthenticated
|
||||
{
|
||||
if(isset($_SESSION['login']))
|
||||
{
|
||||
$response = $response->withRedirect($this->router->pathFor('settings.show'));
|
||||
$response = $response->withRedirect($this->router->pathFor('content.show'));
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
|
@ -16,10 +16,10 @@ class RedirectIfUnauthenticated
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
{
|
||||
if(!isset($_SESSION['login']))
|
||||
{
|
||||
$response = $response->withRedirect($this->router->pathFor('auth.show'));
|
||||
return $response->withRedirect($this->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
|
26
system/Middleware/RestrictApiAccess.php
Normal file
26
system/Middleware/RestrictApiAccess.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Slim\Interfaces\RouterInterface;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class RestrictApiAccess
|
||||
{
|
||||
protected $router;
|
||||
|
||||
public function __construct(RouterInterface $router)
|
||||
{
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
|
||||
{
|
||||
return $response->withJson(['errors' => ['access denied']], 403);
|
||||
}
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ class Folder
|
||||
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
|
||||
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->key = $iteration;
|
||||
$item->keyPath = $keyPath ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPathArray = explode('.', $item->keyPath);
|
||||
$item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
|
||||
|
||||
@ -150,7 +150,7 @@ class Folder
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
/* get the first element in the folder */
|
||||
$item->nextItem = isset($item->folderContent[0]) ? $item->folderContent[0] : false;
|
||||
$item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
|
||||
}
|
||||
|
||||
if(!$item->nextItem)
|
||||
|
@ -201,7 +201,7 @@ class Validation
|
||||
$v = new Validator($params);
|
||||
|
||||
$v->rule('required', ['title', 'content', 'url']);
|
||||
$v->rule('lengthBetween', 'title', 2, 40);
|
||||
$v->rule('lengthBetween', 'title', 2, 100);
|
||||
$v->rule('noHTML', 'title');
|
||||
$v->rule('markdownSecure', 'content');
|
||||
|
||||
|
@ -55,7 +55,12 @@ class Write
|
||||
if($this->checkPath($folder))
|
||||
{
|
||||
$filePath = $this->basePath . $folder . DIRECTORY_SEPARATOR . $file;
|
||||
$openFile = fopen($filePath, "w");
|
||||
$openFile = @fopen($filePath, "w");
|
||||
|
||||
if(!$openFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite($openFile, $data);
|
||||
fclose($openFile);
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
use Typemill\Controllers\SettingsController;
|
||||
use Typemill\Controllers\ContentController;
|
||||
use Typemill\Middleware\RedirectIfUnauthenticated;
|
||||
use Typemill\Middleware\RedirectIfAuthenticated;
|
||||
use Typemill\Middleware\RedirectIfNoAdmin;
|
||||
use Typemill\Middleware\RestrictApiAccess;
|
||||
|
||||
$app->get('/api/v1/themes', SettingsController::class . ':getThemeSettings')->setName('api.themes');
|
||||
$app->put('/api/v1/article', ContentController::class . ':updateArticle')->setName('api.article.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
|
||||
$app->get('/api/v1/themes', SettingsController::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/article', ContentController::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
|
@ -6,28 +6,33 @@
|
||||
<div class="formWrapper">
|
||||
|
||||
<section>
|
||||
<form>
|
||||
<fieldset>
|
||||
<div id="editor" class="editor">
|
||||
<div class="large">
|
||||
<div id="editor" class="editor">
|
||||
<form action="#" @submit.prevent="saveMarkdown">
|
||||
<fieldset>
|
||||
<div class="large" :class="{'error' : errors.title}">
|
||||
<label for="title">Title*</label>
|
||||
<input name="title" id="title" type="text" value="{{ title }}" required />
|
||||
<input id="title" name="title" type="text" v-model="form.title" required />
|
||||
<span class="error" v-if="errors.title">${ errors.title }</span>
|
||||
</div>
|
||||
<input name="url" id="url" type="hidden" value="{{ item.urlRel }}" required />
|
||||
<div class="large">
|
||||
<div class="large" :class="{'error' : errors.content}">
|
||||
<label for="content">Content*</label>
|
||||
<resizable-textarea>
|
||||
<textarea id="content" v-model="markdown" name=="content" required></textarea>
|
||||
<textarea id="content" v-model="form.content" required></textarea>
|
||||
</resizable-textarea>
|
||||
<span class="error" v-if="errors.content">${ errors.content }</span>
|
||||
</div>
|
||||
<input id="path" type="hidden" value="{{ item.urlRel }}" required readonly />
|
||||
<div class="large">
|
||||
<button v-on:click="saveMarkdown">Save</button>
|
||||
<div id="message" class="message"></div>
|
||||
<button :class="bresult" :disabled="bdisabled">Save</button>
|
||||
<div v-if="errors.message" class="message error">${ errors.message }</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<input id="origTitle" style="display:none" value="{{title}}">
|
||||
<textarea id="origContent" style="display:none">{{ content }}</textarea>
|
||||
{{ csrf_field() | raw }}
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
@ -1,5 +1,3 @@
|
||||
const root = document.getElementById("main").dataset.url;
|
||||
|
||||
Vue.component('resizable-textarea', {
|
||||
methods: {
|
||||
resizeTextarea (event) {
|
||||
@ -22,115 +20,58 @@ Vue.component('resizable-textarea', {
|
||||
},
|
||||
});
|
||||
|
||||
new Vue({
|
||||
let app = new Vue({
|
||||
delimiters: ['${', '}'],
|
||||
el: '#editor',
|
||||
data: {
|
||||
markdown: document.getElementById("origContent").value
|
||||
form: {
|
||||
title: document.getElementById("origTitle").value,
|
||||
content: document.getElementById("origContent").value,
|
||||
url: document.getElementById("path").value,
|
||||
csrf_name: document.getElementById("csrf_name").value,
|
||||
csrf_value: document.getElementById("csrf_value").value,
|
||||
},
|
||||
root: document.getElementById("main").dataset.url,
|
||||
errors:{
|
||||
title: false,
|
||||
content: false,
|
||||
message: false,
|
||||
},
|
||||
bdisabled: false,
|
||||
bresult: false,
|
||||
},
|
||||
methods: {
|
||||
saveMarkdown: function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var self = this;
|
||||
self.errors = {title: false, content: false, message: false},
|
||||
self.bresult = '';
|
||||
self.bdisabled = "disabled";
|
||||
|
||||
var url = this.root + '/api/v1/article';
|
||||
var method = 'PUT';
|
||||
|
||||
e.target.disabled = true;
|
||||
e.target.classList.remove("success", "fail");
|
||||
|
||||
deleteErrors();
|
||||
|
||||
var getPost = 'PUT',
|
||||
url = root + '/api/v1/article',
|
||||
contentData = {'url': document.getElementById("url").value, 'title': document.getElementById("title").value, 'content': document.getElementById("content").value };
|
||||
|
||||
sendJson(function(response, httpStatus)
|
||||
{
|
||||
if(response)
|
||||
{
|
||||
e.target.disabled = false;
|
||||
self.bdisabled = false;
|
||||
|
||||
var result = JSON.parse(response);
|
||||
|
||||
if(result.errors)
|
||||
{
|
||||
e.target.classList.add('fail');
|
||||
processErrors(result.errors, httpStatus);
|
||||
self.bresult = 'fail';
|
||||
if(result.errors.title){ self.errors.title = result.errors.title[0] };
|
||||
if(result.errors.content){ self.errors.content = result.errors.content[0] };
|
||||
if(result.errors.message){ self.errors.message = result.errors.message };
|
||||
}
|
||||
else
|
||||
{
|
||||
e.target.classList.add('success');
|
||||
self.bresult = 'success';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
e.target.disabled = false;
|
||||
e.target.classList.add('fail');
|
||||
console.info('no response');
|
||||
}
|
||||
}, getPost, url, contentData );
|
||||
}, method, url, this.form );
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function processErrors(errors, httpStatus)
|
||||
{
|
||||
if(errors.length == 0) return;
|
||||
|
||||
var message = '';
|
||||
|
||||
if(httpStatus == "404")
|
||||
{
|
||||
message = errors[0];
|
||||
}
|
||||
|
||||
if(httpStatus == "422")
|
||||
{
|
||||
var fields = '';
|
||||
|
||||
for (var key in errors)
|
||||
{
|
||||
fields = fields + ' "' + key + '"';
|
||||
|
||||
if(key == 'url' || !errors.hasOwnProperty(key)) continue;
|
||||
|
||||
|
||||
var errorMessages = errors[key],
|
||||
fieldElement = document.getElementById(key),
|
||||
fieldMessage = document.createElement("span"),
|
||||
fieldWrapper = fieldElement.parentElement;
|
||||
|
||||
fieldWrapper.classList.add("error");
|
||||
|
||||
fieldMessage.className = "error";
|
||||
fieldMessage.innerHTML = errorMessages[0];
|
||||
fieldWrapper.classList.add("error");
|
||||
fieldWrapper.appendChild(fieldMessage);
|
||||
}
|
||||
|
||||
message = 'Please correct the errors in these Fields: ' + fields.toUpperCase() + '. ';
|
||||
}
|
||||
|
||||
var messageWrapper = document.getElementById("message"),
|
||||
messageSpan = document.createElement("span");
|
||||
|
||||
messageSpan.className = "error";
|
||||
messageSpan.innerHTML = message;
|
||||
messageWrapper.appendChild(messageSpan);
|
||||
}
|
||||
|
||||
function deleteErrors()
|
||||
{
|
||||
var errors = document.querySelectorAll('.error');
|
||||
|
||||
if(errors.length == 0) return;
|
||||
|
||||
for(var key in errors)
|
||||
{
|
||||
if(!errors.hasOwnProperty(key)) continue;
|
||||
|
||||
if(errors[key].tagName == "SPAN")
|
||||
{
|
||||
errors[key].parentElement.removeChild(errors[key]);
|
||||
}
|
||||
else
|
||||
{
|
||||
errors[key].classList.remove("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -1,6 +1,7 @@
|
||||
{% extends 'layouts/layout.twig' %}
|
||||
{% block title %}Setup{% endblock %}
|
||||
{% set startpage = old.settings.startpage ? old.settings.startpage : settings.startpage %}
|
||||
{% set linebreaks = old.settings.linebreaks ? old.settings.linebreaks : settings.linebreaks %}
|
||||
{% set year = settings.year ? settings.year : "now"|date("Y") %}
|
||||
|
||||
{% block content %}
|
||||
@ -66,9 +67,8 @@
|
||||
<input name="settings[startpage]" type="checkbox" id="startpage"{{ startpage ? ' checked' : '' }}>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</section>
|
||||
<input type="submit" value="Save All Settings" />
|
||||
|
||||
|
@ -103,7 +103,7 @@ $container['assets'] = function($c)
|
||||
* DECIDE FOR SESSION *
|
||||
************************/
|
||||
|
||||
$session_segments = array('setup', 'tm/', '/setup', '/tm/');
|
||||
$session_segments = array('setup', 'tm/', 'api/', '/setup', '/tm/', '/api/');
|
||||
$path = $container['request']->getUri()->getPath();
|
||||
$container['flash'] = false;
|
||||
$container['csrf'] = false;
|
||||
@ -112,7 +112,7 @@ foreach($session_segments as $segment)
|
||||
{
|
||||
if(substr( $path, 0, strlen($segment) ) === $segment)
|
||||
{
|
||||
/* start a session */
|
||||
// configure session
|
||||
ini_set( 'session.cookie_httponly', 1 );
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
if($container['request']->getUri()->getScheme() == 'https')
|
||||
@ -124,9 +124,8 @@ foreach($session_segments as $segment)
|
||||
{
|
||||
session_name('typemill-session');
|
||||
}
|
||||
session_start();
|
||||
|
||||
/* add csrf-protection */
|
||||
// add csrf-protection
|
||||
$container['csrf'] = function ($c)
|
||||
{
|
||||
$guard = new \Slim\Csrf\Guard();
|
||||
@ -135,11 +134,14 @@ foreach($session_segments as $segment)
|
||||
return $guard;
|
||||
};
|
||||
|
||||
/* add flash to container */
|
||||
// add flash to container
|
||||
$container['flash'] = function ()
|
||||
{
|
||||
return new \Slim\Flash\Messages();
|
||||
};
|
||||
|
||||
// start session
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,14 @@
|
||||
<div class="lead">
|
||||
|
||||
{{ content }}
|
||||
|
||||
<a href="{{ navigation[0].urlRel }}">{{ settings.themes.typemill.start ? settings.themes.typemill.start : 'Start'}}</a>
|
||||
|
||||
{% if settings.setup %}
|
||||
<a href="{{ base_url }}/setup">Setup</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="actionLink">
|
||||
<a href="{{ navigation[0].urlRel }}">{{ settings.themes.typemill.start ? settings.themes.typemill.start : 'Start'}}</a>
|
||||
|
||||
{% if settings.setup %}
|
||||
<a href="{{ base_url }}/setup">Setup</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -21,9 +21,11 @@ aside{ background: #f9f8f6; border-left: 30px solid #FFF; border-right: 30px sol
|
||||
.main-menu li a:focus, .main-menu li a:hover, .main-menu li a:active, .main-menu li.active.file a{ color: #e0474c; }
|
||||
article {background: #FFF; }
|
||||
article a, article a:link, article a:visited,
|
||||
footer a, footer a:link, footer a:visited{ text-decoration: none; color: #e0474c; }
|
||||
footer a, footer a:link, footer a:visited
|
||||
.lead a, .lead a:link, .lead a:visited{ text-decoration: none; color: #e0474c; }
|
||||
article a:focus, article a:hover, article a:active,
|
||||
footer a:focus, footer a:hover, footer a:active{ text-decoration: underline }
|
||||
footer a:focus, footer a:hover, footer a:active
|
||||
.lead a:focus, .lead a:hover, .lead a:active{ text-decoration: underline }
|
||||
article .breadcrumb,article .paging a{ background: #f9f8f6; }
|
||||
article .breadcrumb span a{ background: #e0474c; color: #f9f8f6; border: 1px solid #e0474c; }
|
||||
article .breadcrumb a:focus,article .breadcrumb a:hover,article .breadcrumb a:active { background: #f9f8f6; color: #e0474c; }
|
||||
@ -36,9 +38,9 @@ header a, .cover{ color: #444; }
|
||||
footer{ background: #FFF; }
|
||||
.chapterNumber{ color: #bbb; }
|
||||
.chapter h1{ border-bottom: 2px solid #f9f8f6; }
|
||||
.cover .lead a, .cover .lead a:link, .cover .lead a:visited,
|
||||
.cover .actionLink a, .cover .actionLink a:link, .cover .actionLink a:visited,
|
||||
a.readMore, a.readMore:link, a.readMore:visited{ border: 2px solid #e0474c; background: #e0474c; color: #f9f8f6; }
|
||||
.cover .lead a:focus, .cover .lead a:hover, .cover .lead a:active,
|
||||
.cover .lead a:focus, .cover .actionLink a:hover, .cover .actionLink a:active,
|
||||
a.readMore:focus, a.readMore:hover, a.readMore:active{
|
||||
border: 2px solid #e0474c;
|
||||
color: #444;
|
||||
@ -204,7 +206,7 @@ header p{
|
||||
font-size: 2.5em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cover .lead a, a.readMore{
|
||||
.cover .actionLink a, a.readMore{
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
padding: 5px 10px;
|
||||
@ -319,6 +321,25 @@ article{
|
||||
article img{
|
||||
width: 100%;
|
||||
}
|
||||
article img.left{
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
float: left;
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
article img.right{
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
float: right;
|
||||
margin: 10px 0px 10px 10px;
|
||||
}
|
||||
article img.middle{
|
||||
display:block;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
||||
/************************
|
||||
* PAGING / BREADCRUMB *
|
||||
|
Loading…
x
Reference in New Issue
Block a user