فهرست منبع

Version 1.2.15: Beautiful Math Refactored

Trendschau 6 سال پیش
والد
کامیت
3fce3a5c61
36فایلهای تغییر یافته به همراه723 افزوده شده و 249 حذف شده
  1. 1 1
      cache/lastCache.txt
  2. 17 8
      content/00-Welcome/03-Markdown-Test.md
  3. 0 0
      content/baseItem.txt
  4. 1 1
      content/index.md
  5. BIN
      media/live/SYKMLbMfD_M-live.jpeg
  6. BIN
      media/live/c-PyqAz6XyU-live.jpeg
  7. BIN
      media/original/SYKMLbMfD_M-original.jpeg
  8. BIN
      media/original/c-PyqAz6XyU-original.jpeg
  9. 6 3
      plugins/math/math.php
  10. 1 1
      plugins/math/math.yaml
  11. 0 0
      plugins/math/public/katex.min.css
  12. 0 0
      plugins/math/public/katex.min.js
  13. 0 10
      settings/README.md
  14. 22 1
      system/Controllers/ContentApiController.php
  15. 13 11
      system/Controllers/ContentBackendController.php
  16. 31 0
      system/Controllers/ContentController.php
  17. 4 4
      system/Controllers/PageController.php
  18. 6 2
      system/Controllers/SettingsController.php
  19. 2 2
      system/Controllers/SetupController.php
  20. 91 120
      system/Extensions/ParsedownExtension.php
  21. 165 0
      system/Extensions/ParsedownMath.php
  22. 39 11
      system/Models/Folder.php
  23. 1 0
      system/Routes/Api.php
  24. 36 15
      system/Settings.php
  25. 1 1
      system/author/auth/welcome.twig
  26. 30 10
      system/author/css/style.css
  27. 2 1
      system/author/editor/editor-blox.twig
  28. 57 30
      system/author/js/lazy-video.js
  29. 138 3
      system/author/js/vue-blox.js
  30. 34 1
      system/author/js/vue-navi.js
  31. 3 0
      system/author/js/vue-publishcontroller.js
  32. 6 0
      system/author/layouts/layoutBlox.twig
  33. 6 5
      system/author/partials/editorNavi.twig
  34. 5 5
      system/system.php
  35. 3 1
      themes/typemill/css/style.css
  36. 2 2
      themes/typemill/partials/layout.twig

+ 1 - 1
cache/lastCache.txt

@@ -1 +1 @@
-1558126279
+1559660578

+ 17 - 8
content/00-Welcome/03-Markdown-Test.md

@@ -263,7 +263,7 @@ Let us create some `<?php inlineCode(); ?>` and now let us check, if a codeblock
 
 ````
 Use four apostroph like this:  
-\````
+\````
 <?php
 	$welcome = 'Hello World!';
 	echo $welcome;
@@ -276,21 +276,30 @@ Use four apostroph like this:
 Please activate the math-plugin to use mathematical expressions with LaTeX syntax. You can choose between MathJax or the newer KaTeX library. MathJax is included from a CDN, KaTeX is included in the plugin. So if you don't want to fetch code from a CDN, use KaTeX instead. The markdown syntax in TYPEMILL is the same for both libraries.
 
 ````
-Use inline LaTeX  ``x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)`` with two backtipps like this.
+Write inline math with \(...\) or $...$ syntax.
+inline $x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)$ math
+inline \(x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)\) math
 ````
 
-Use inline LaTeX ``x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)`` like this. 
+inline $x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)$ math
+
+inline \(x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)\) math
 
 ````
-Or specify latex sytnax for a code-block like this:  
-​````latex
+Write display math with $$...$$ or \[...\] syntax.  
+$$
 x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
-​````  
+$$
+\[
+x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
+\]
 ````
 
-​````latex
+$$
 x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
-​````
+$$
+
+Das war es dann aber auch.
 
 [^1]: Thank you for scrolling.
 [^2]: This is the end of the page.

+ 0 - 0
content/baseItem.txt


+ 1 - 1
content/index.md

@@ -1,4 +1,4 @@
 # Typemill
 
-*Typemill is a user-friendly and lightweight open source CMS for publishing text-works like prose, lyrics, manuals, documentations, studies and more. Just download and start.*
+*Typemill is a user-friendly and lightweight open source CMS for publishing text-works like prose, lyrics, manuals, documentations, studies and more. Just download and start.* edkit
 

BIN
media/live/SYKMLbMfD_M-live.jpeg


BIN
media/live/c-PyqAz6XyU-live.jpeg


BIN
media/original/SYKMLbMfD_M-original.jpeg


BIN
media/original/c-PyqAz6XyU-original.jpeg


+ 6 - 3
plugins/math/math.php

@@ -37,8 +37,11 @@ class Math extends Plugin
 			$this->addJS('/math/public/auto-render.min.js');
 			$this->addCSS('/math/public/katex.min.css');
 
-			/* initialize autorendering of page */
-			$this->addInlineJs('renderMathInElement(document.body);');
-		}		
+			/* initialize autorendering of page only in frontend */
+			if (strpos($this->getPath(), 'tm/content') === false) 
+			{
+				$this->addInlineJs('renderMathInElement(document.body);');
+			}
+		}
 	}
 }

+ 1 - 1
plugins/math/math.yaml

@@ -1,5 +1,5 @@
 name: Math
-version: 1.0.1
+version: 1.0.2
 description: Adds support for katex and mathjax.
 author: Sebastian Schürmanns
 homepage: https://mathjax.org/

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
plugins/math/public/katex.min.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
plugins/math/public/katex.min.js


+ 0 - 10
settings/README.md

@@ -1,10 +0,0 @@
-Settings are generated during the setup of the system.
-
-If you are a developer, you can manually add the settings
-
-````
-displayErrorDetails: true
-````
-
-This will display the php errors during the developement process. Do not forget to disable the settings for the live environment (delete it or set it to false).
-

+ 22 - 1
system/Controllers/ContentApiController.php

@@ -502,7 +502,28 @@ class ContentApiController extends ContentController
 
 		return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
 	}
-	
+
+	public function getNavigation(Request $request, Response $response, $args)
+	{
+		# get params from call
+		$this->params 	= $request->getParams();
+		$this->uri 		= $request->getUri();
+
+		# set structure
+		if(!$this->setStructure($draft = true, $cache = false)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
+
+		# set information for homepage
+		$this->setHomepage();
+
+		# get item for url and set it active again
+		if(isset($this->params['url']))
+		{
+			$activeItem = Folder::getItemForUrl($this->structure, $this->params['url']);
+		}
+
+		return $response->withJson(array('data' => $this->structure, 'homepage' => $this->homepage, 'errors' => false));
+	}
+
 	public function getArticleMarkdown(Request $request, Response $response, $args)
 	{
 		/* get params from call */

+ 13 - 11
system/Controllers/ContentBackendController.php

@@ -23,10 +23,13 @@ class ContentBackendController extends ContentController
 		# get params from call
 		$this->uri 		= $request->getUri();
 		$this->params	= isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
-				
+		
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
 		
+		# set information for homepage
+		$this->setHomepage();
+
 		# set item
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
 		
@@ -72,11 +75,11 @@ class ContentBackendController extends ContentController
 			}
 		}
 
-		return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
+		return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
 	}
 	
 	/**
-	* Show Content for raw editor
+	* Show Content for blox editor
 	* 
 	* @param obj $request the slim request object
 	* @param obj $response the slim response object
@@ -85,20 +88,19 @@ class ContentBackendController extends ContentController
 	
 	public function showBlox(Request $request, Response $response, $args)
 	{
-		
 		# get params from call
 		$this->uri 		= $request->getUri();
 		$this->params	= isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
-		
+
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
-		
+
+		# set information for homepage
+		$this->setHomepage();
+
 		# set item
 		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
-		
-		# get the breadcrumb (here we need it only to mark the actual item active in navigation)
-		$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;
-		
+
 		# set the status for published and drafted
 		$this->setPublishStatus();
 		
@@ -148,7 +150,7 @@ class ContentBackendController extends ContentController
 			unset($content[0]);			
 		}
 
-		return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
+		return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
 	}
 	
 	public function showEmpty(Request $request, Response $response, $args)

+ 31 - 0
system/Controllers/ContentController.php

@@ -36,6 +36,9 @@ abstract class ContentController
 	# holds the name of the structure-file without drafts for live site 
 	protected $structureLiveName;
 	
+	# holds informations about the homepage
+	protected $homepage;
+
 	# hold the page-item as an object
 	protected $item;
 	
@@ -188,6 +191,34 @@ abstract class ContentController
 		return true;
 	}
 
+	protected function setHomepage()
+	{
+		$contentFolder = Folder::scanFolderFlat($this->settings['rootPath'] . $this->settings['contentFolder']);
+
+		if(array_search('index.md', $contentFolder))
+		{
+			$md = true;
+			$status = 'published';
+		}
+		if(array_search('index.txt', $contentFolder))
+		{
+			$txt = true;
+			$status = 'unpublished';
+		}
+		if(isset($txt) && isset($md))
+		{
+			$status = 'modified';
+		}
+
+		$active = false;
+		if($this->params['url'] == '/' || $this->params['url'] == $this->uri->getBasePath() )
+		{
+			$active = 'active';
+		}
+
+		$this->homepage = ['status' => $status, 'active' => $active];
+	}
+
 	protected function setItem()
 	{
 		# if it is the homepage

+ 4 - 4
system/Controllers/PageController.php

@@ -103,7 +103,7 @@ class PageController extends Controller
 			$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
 			
 			/* check if url is a folder. If so, check if there is an index-file in that folder */
-			if($item->elementType == 'folder' && $item->index)
+			if($item->elementType == 'folder')
 			{
 				$filePath = $pathToContent . $item->path . DIRECTORY_SEPARATOR . 'index.md';
 			}
@@ -126,7 +126,7 @@ class PageController extends Controller
 		
 		/* initialize parsedown */
 		$parsedown 		= new ParsedownExtension();
-
+		
 		/* set safe mode to escape javascript and html in markdown */
 		$parsedown->setSafeMode(true);
 
@@ -136,7 +136,7 @@ class PageController extends Controller
 		
 		/* get the first image from content array */
 		$firstImage		= $this->getFirstImage($contentArray);
-		
+
 		$itemUrl 		= isset($item->urlRel) ? $item->urlRel : false;
 		
 		/* parse markdown-content-array to content-string */
@@ -175,7 +175,7 @@ class PageController extends Controller
 		}
 		
 		$route = empty($args) && $settings['startpage'] ? '/cover.twig' : '/index.twig';
-		
+
 		return $this->render($response, $route, array('navigation' => $structure, 'content' => $contentHTML, 'item' => $item, 'breadcrumb' => $breadcrumb, 'settings' => $settings, 'title' => $title, 'description' => $description, 'base_url' => $base_url, 'image' => $firstImage ));
 	}
 

+ 6 - 2
system/Controllers/SettingsController.php

@@ -216,7 +216,7 @@ class SettingsController extends Controller
 			$uri 			= $request->getUri();
 			$base_url		= $uri->getBaseUrl();
 
-			# security, users should not be able to fake post with settings from other typemill pages.
+			# users should not be able to fake post with settings from other typemill pages.
 			if(!isset($referer[0]) OR $referer[0] !== $base_url . '/tm/themes' )
 			{
 				$this->c->flash->addMessage('error', 'illegal referer');
@@ -232,7 +232,11 @@ class SettingsController extends Controller
 			
 			if(isset($themeSettings['settings']['images']))
 			{	
-				$userSettings 	= ['images' => $themeSettings['settings']['images']];
+				# get the default settings
+				$defaultSettings = \Typemill\Settings::getDefaultSettings();
+				
+				# merge the default image settings with the theme image settings, delete all others (image settings from old theme)
+				$userSettings['images'] = array_merge($defaultSettings['images'], $themeSettings['settings']['images']);
 			}
 			
 			/* set theme name and delete theme settings from user settings for the case, that the new theme has no settings */

+ 2 - 2
system/Controllers/SetupController.php

@@ -54,8 +54,8 @@ class SetupController extends Controller
 					/* login user */
 					$user->login($username);
 
-					/* store updated settings */
-					\Typemill\Settings::createSettings(array('setup' => false));
+					# create initial settings file
+					\Typemill\Settings::createSettings();
 					
 					return $response->withRedirect($this->c->router->pathFor('setup.welcome'));
 				}

+ 91 - 120
system/Extensions/ParsedownExtension.php

@@ -10,14 +10,19 @@ class ParsedownExtension extends \ParsedownExtra
     {
 		parent::__construct();
 
-		# mathjax support
-        $this->InlineTypes['`'][] = 'MathJaxLaTeX';
-        $this->BlockTypes['`'][] = 'FencedMathJaxLaTeX';
-		
+        # math support
+        $this->BlockTypes['\\'][] = 'Math';
+        $this->BlockTypes['$'][] = 'Math';
+        
+        $this->InlineTypes['\\'][] = 'Math';
+        $this->InlineTypes['$'][] = 'Math';
+        $this->inlineMarkerList .= '\\';
+        $this->inlineMarkerList .= '$';
+
 		# table of content support
         array_unshift($this->BlockTypes['['], 'TableOfContents');
     }
-		
+	
 	public function text($text)
 	{
         $Elements = $this->textElements($text);
@@ -72,7 +77,7 @@ class ParsedownExtension extends \ParsedownExtra
 
         return $footnotes;
     }
-			
+
     # TableOfContents
 
     protected function blockTableOfContents($line, $block)
@@ -125,8 +130,8 @@ class ParsedownExtension extends \ParsedownExtra
         }
     }
 	
-	# build the markup for table of contents 
-	
+    # build the markup for table of contents 
+    
 	public function buildTOC($headlines)
 	{
 		$markup = '<ul class="TOC">';
@@ -165,40 +170,23 @@ class ParsedownExtension extends \ParsedownExtra
 
     #
     # Footnote Marker
+    # add absolute url
 
     protected function inlineFootnoteMarker($Excerpt)
     {
-        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
-        {
-            $name = $matches[1];
 
-            if ( ! isset($this->DefinitionData['Footnote'][$name]))
-            {
-                return;
-            }
+        $element = parent::inlineFootnoteMarker($Excerpt);
 
-            $this->DefinitionData['Footnote'][$name]['count'] ++;
-
-            if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
-            {
-                $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
-            }
+        if ( ! isset($element))
+        {
+            return null;
+        }
+   
+        $href = $element['element']['element']['attributes']['href'];
 
-            $Element = array(
-                'name' => 'sup',
-                'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
-                'element' => array(
-                    'name' => 'a',
-                    'attributes' => array('href' => $this->relurl . '#fn:' . $name, 'class' => 'footnote-ref'),
-                    'text' => $this->DefinitionData['Footnote'][$name]['number'],
-                ),
-            );
+        $element['element']['element']['attributes']['href'] = $this->relurl . $href;
 
-            return array(
-                'extent' => strlen($matches[0]),
-                'element' => $Element,
-            );
-        }
+        return $element;
     }
 	
 	public $footnoteCount = 0;
@@ -295,129 +283,112 @@ class ParsedownExtension extends \ParsedownExtra
 
         return $Element;
     }
-	
-	# math support. Check https://github.com/aidantwoods/parsedown/blob/mathjaxlatex/ParsedownExtensionMathJaxLaTeX.php
+    
+    # Inline Math
+    # check https://github.com/BenjaminHoegh/ParsedownMath
+    # check https://github.com/cben/mathdown/wiki/math-in-markdown
 
-    protected function inlineCode($Excerpt)
+    protected function inlineMath($Excerpt)
     {
-        $marker = $Excerpt['text'][0];
-        if (preg_match('/^('.$marker.')[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+        if(preg_match('/^(?<!\\\\)(?<!\\\\\()\\\\\((.*?)(?<!\\\\\()\\\\\)(?!\\\\\))/s', $Excerpt['text'], $matches) OR preg_match('/\$(?!\$)([^ ][^\$\n]+)(?<! )\$(?![1-9])/s', $Excerpt['text'], $matches))
         {
-            $text = $matches[2];
-            $text = preg_replace("/[ ]*\n/", ' ', $text);
             return array(
                 'extent' => strlen($matches[0]),
                 'element' => array(
-                    'name' => 'code',
-                    'text' => $text,
+                    'text' => '\(' . $matches[1] . '\)',
                 ),
-            );
+            );        
         }
-    }	
-	
-    protected function inlineMathJaxLaTeX($Excerpt)
+    }
+    
+    protected $specialCharacters = array(
+        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '<', '>', '#', '+', '-', '.', '!', '|', '~', '^', '='
+    );
+
+    //
+    // Inline Escape
+    // -------------------------------------------------------------------------
+    protected function inlineEscapeSequence($Excerpt)
     {
-        $marker = $Excerpt['text'][0];
-        if (preg_match('/^('.$marker.'{2,})[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
-        {
-            $text = $matches[2];
-            $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
-            $text = preg_replace("/[ ]*\n/", ' ', $text);
+        if (isset($Excerpt['text'][1]) 
+            && in_array($Excerpt['text'][1], $this->specialCharacters) 
+            && !preg_match('/(?<!\\\\)((?<!\\\\\()\\\\\((?!\\\\\())(.*?)(?<!\\\\)(?<!\\\\\()((?<!\\\\\))\\\\\)(?!\\\\\)))(?!\\\\\()/s', $Excerpt['text'])
+            && !preg_match('/\$(?!\$)([^ ][^\$\n]+)(?<! )\$(?![1-9])/s', $Excerpt['text'])
+        ){
             return array(
-                'extent' => strlen($matches[0]),
                 'element' => array(
-                    'name' => 'span',
-                    'text' => '\('.$text.'\)',
+                    'rawHtml' => $Excerpt['text'][1],
                 ),
+                'extent' => 2,
             );
         }
     }
-	
-    #
-    # Fenced Code
-    protected function blockFencedCode($Line)
+
+    # Block Math
+    protected function blockMath($Line)
     {
-        if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
-        {
-            $Element = array(
-                'name' => 'code',
+        $Block = array(
+            'element' => array(
                 'text' => '',
-            );
-			
-            if (isset($matches[2]))
-            {
-                if (strtolower($matches[2]) === 'latex')
-                {
-                    return;
-                }
-                $class = 'language-'.$matches[2];
-                $Element['attributes'] = array(
-                    'class' => $class,
-                );
-            }
-            $Block = array(
-                'char' => $Line['text'][0],
-                'openerLength' => mb_strlen($matches[1]),
-                'element' => array(
-                    'name' => 'pre',
-                    'element' => $Element,
-                ),
-            );
-			
+            ),
+        );
+        if (preg_match('/^(?<!\\\\)(\\\\\[)(?!.)$/', $Line['text'])) 
+        {
+            $Block['end'] = '\]';
             return $Block;
-        }
-    }
-
-    #
-    # Fenced MathJax
-    protected function blockFencedMathJaxLaTeX($Line)
-    {
-        if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
+        } 
+        elseif (preg_match('/^(?<!\\\\)(\$\$)(?!.)$/', $Line['text']))
         {
-            if ( ! isset($matches[1]) or strtolower($matches[1]) !== 'latex')
-            {
-                return;
-            }
-            $Block = array(
-                'char' => $Line['text'][0],
-                'element' => array(
-                    'name' => 'span',
-                    'text' => '',
-                ),
-            );
+            $Block['end'] = '$$';
             return $Block;
         }
     }
-	
-    protected function blockFencedMathJaxLaTeXContinue($Line, $Block)
+   
+    // ~
+    protected function blockMathContinue($Line, $Block)
     {
-
-		if (isset($Block['complete']))
+        if (isset($Block['complete'])) 
         {
             return;
         }
-        if (isset($Block['interrupted']))
+        if (isset($Block['interrupted'])) 
         {
-            $Block['element']['text'] .= "\n";
+            $Block['element']['text'] .= str_repeat("\n", $Block['interrupted']);
             unset($Block['interrupted']);
         }
-        if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+        if ($Block['end'] === '\]' && preg_match('/^(?<!\\\\)(\\\\\])$/', $Line['text']))
+        {
+            $Block['complete'] = true;
+            $Block['latex'] = true;
+            $Block['element']['name'] = 'div';
+            $Block['element']['text'] = "\\[".$Block['element']['text']."\n\\]";
+            $Block['element']['attributes'] = array('class' => 'math');
+
+            return $Block;
+        } 
+        elseif ($Block['end'] === '$$' && preg_match('/^(?<!\\\\)(\$\$)$/', $Line['text'])) 
         {
-            $Block['element']['text'] = substr($Block['element']['text'], 1);
             $Block['complete'] = true;
+            $Block['latex'] = true;
+            $Block['element']['name'] = 'div';
+            $Block['element']['text'] = "$$".$Block['element']['text']."\n$$";
+            $Block['element']['attributes'] = array('class' => 'math');
+
             return $Block;
         }
-        $Block['element']['text'] .= "\n".$Line['body'];;
+
+        $Block['element']['text'] .= "\n" . $Line['body'];
+        
+        // ~
         return $Block;
     }
-	
-    protected function blockFencedMathJaxLaTeXComplete($Block)
+
+    // ~
+    protected function blockMathComplete($Block)
     {
-        $text = $Block['element']['text'];
-        $Block['element']['text'] = "\$\$\n" . $text . "\n\$\$";
         return $Block;
     }
-	
+        
 	# advanced attribute data, check parsedown extra plugin: https://github.com/tovic/parsedown-extra-plugin
     protected function parseAttributeData($text) {
         // Allow compact attributes ...

+ 165 - 0
system/Extensions/ParsedownMath.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace Typemill\Extensions;
+
+class ParsedownMath extends \ParsedownExtra
+{
+    const VERSION = '1.0';
+
+    public function __construct()
+    {
+
+        if (version_compare(parent::version, '1.7.1') < 0) {
+            # die('need version 1.7.1');
+            # throw new Exception('ParsedownMath requires a later version of Parsedown');
+        }
+
+        // Blocks
+        $this->BlockTypes['\\'][] = 'Math';
+        $this->BlockTypes['$'][] = 'Math';
+
+        // Inline
+        $this->InlineTypes['\\'][] = 'Math';
+        $this->inlineMarkerList .= '\\';
+    }
+
+    // Setters
+
+    protected $mathMode = true;
+
+    public function enableMath($input = true)
+    {
+        $this->mathMode = $input;
+
+        if ($input == false) {
+            return $this;
+        }
+
+        return $this;
+    }
+
+
+    // -------------------------------------------------------------------------
+    // -----------------------         Inline         --------------------------
+    // -------------------------------------------------------------------------
+
+
+    //
+    // Inline Math
+    // -------------------------------------------------------------------------
+
+    protected function inlineMath($Excerpt)
+    {
+        if (!$this->mathMode) {
+            return;
+        }
+
+        // if (preg_match('/^(?<!\\\\)((?<!\\\\\()\\\\\((?!\\\\\())(.*?)(?<!\\\\)(?<!\\\\\()((?<!\\\\\))\\\\\)(?!\\\\\)))(?!\\\\\()/s', $Excerpt['text'], $matches)) {
+        if (preg_match('/^(?<!\\\\)(?<!\\\\\()\\\\\((.*?)(?<!\\\\\()\\\\\)(?!\\\\\))/s', $Excerpt['text'], $matches)) {
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'text' =>  $matches[0]
+                ),
+            );
+        }
+    }
+
+    protected $specialCharacters = array(
+        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '<', '>', '#', '+', '-', '.', '!', '|', '~', '^', '='
+    );
+
+
+    //
+    // Inline Escape
+    // -------------------------------------------------------------------------
+
+    protected function inlineEscapeSequence($Excerpt)
+    {
+        $Element = array(
+            'element' => array(
+                'rawHtml' => $Excerpt['text'][1],
+            ),
+            'extent' => 2,
+        );
+
+        if ($this->mathMode) {
+            if (isset($Excerpt['text'][1]) && in_array($Excerpt['text'][1], $this->specialCharacters) && !preg_match('/(?<!\\\\)((?<!\\\\\()\\\\\((?!\\\\\())(.*?)(?<!\\\\)(?<!\\\\\()((?<!\\\\\))\\\\\)(?!\\\\\)))(?!\\\\\()/s', $Excerpt['text'])) {
+                return $Element;
+            }
+        } else {
+            if (isset($Excerpt['text'][1]) && in_array($Excerpt['text'][1], $this->specialCharacters)) {
+                return $Element;
+            }
+        }
+    }
+
+
+
+    // -------------------------------------------------------------------------
+    // -----------------------         Blocks         --------------------------
+    // -------------------------------------------------------------------------
+
+
+    //
+    // Block Math
+    // --------------------------------------------------------------------------
+
+    protected function blockMath($Line)
+    {
+        $Block = array(
+            'element' => array(
+                'text' => '',
+            ),
+        );
+
+        if (preg_match('/^(?<!\\\\)(\\\\\[)(?!.)$/', $Line['text'])) {
+            $Block['end'] = '\]';
+            return $Block;
+        } elseif (preg_match('/^(?<!\\\\)(\$\$)(?!.)$/', $Line['text'])) {
+            $Block['end'] = '$$';
+            return $Block;
+        }
+    }
+
+    // ~
+
+    protected function blockMathContinue($Line, $Block)
+    {
+        if (isset($Block['complete'])) {
+            return;
+        }
+
+        if (isset($Block['interrupted'])) {
+            $Block['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+            unset($Block['interrupted']);
+        }
+
+        if (preg_match('/^(?<!\\\\)(\\\\\])$/', $Line['text']) && $Block['end'] === '\]') {
+            $Block['complete'] = true;
+            $Block['latex'] = true;
+            $Block['element']['text'] = "\\[".$Block['element']['text']."\\]";
+            return $Block;
+        } elseif (preg_match('/^(?<!\\\\)(\$\$)$/', $Line['text']) && $Block['end'] === '$$') {
+            $Block['complete'] = true;
+            $Block['latex'] = true;
+            $Block['element']['text'] = "$$".$Block['element']['text']."$$";
+            return $Block;
+        }
+
+
+        $Block['element']['text'] .= "\n" . $Line['body'];
+
+        // ~
+
+        return $Block;
+    }
+
+    // ~
+
+    protected function blockMathComplete($Block)
+    {
+        return $Block;
+    }
+}

+ 39 - 11
system/Models/Folder.php

@@ -43,16 +43,18 @@ class Folder
 	{
 		$folderItems 	= scandir($folderPath);
 		$folderContent 	= array();
-		
+
+		# if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
+		if(!$draft && !in_array('index.md', $folderItems)){ return false; }
+
 		foreach ($folderItems as $key => $item)
 		{
 			if (!in_array($item, array(".","..")))
 			{
 				if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
 				{
-					/* TODO: if folder is empty or folder has only txt files, continue */
 					$subFolder 					= $item;
-					$folderContent[$subFolder] 	= self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);					
+					$folderContent[$subFolder] 	= self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
 				}
 				else
 				{
@@ -69,6 +71,7 @@ class Folder
 						if(isset($last) && ($last == implode($nameParts)) )
 						{
 							array_pop($folderContent);
+							$item = $item . 'md';
 						}
 						$folderContent[] = $item;
 					}
@@ -91,7 +94,7 @@ class Folder
 		$contentDetails 	= [];
 		$iteration 			= 0;
 		$chapternr 			= 1;
-		
+
 		foreach($folderContent as $key => $name)
 		{
 			$item = new \stdClass();
@@ -100,19 +103,26 @@ class Folder
 			{
 				$nameParts = self::getStringParts($key);
 				
-				$fileType = false; 
-				if(array_search('index.md', $name))
+				$fileType = '';
+				if(in_array('index.md', $name))
 				{
 					$fileType = 'md';
+					$status = 'published';
 				}
-				elseif(array_search('index.txt', $name))
+				if(in_array('index.txt', $name))
 				{
 					$fileType = 'txt';
+					$status = 'unpublished';
+				}
+				if(in_array('index.txtmd', $name))
+				{
+					$fileType = 'txt';
+					$status = 'modified';
 				}
 
 				$item->originalName 	= $key;
 				$item->elementType		= 'folder';
-				$item->index			= $fileType;
+				$item->status			= $status;
 				$item->fileType			= $fileType;
 				$item->order 			= count($nameParts) > 1 ? array_shift($nameParts) : NULL;
 				$item->name 			= implode(" ",$nameParts);
@@ -134,14 +144,32 @@ class Folder
 			}
 			else
 			{
+				# do not use files in base folder (only folders are allowed)
+				if(!isset($keyPath)) continue;
+
+				# do not use index files
+				if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
+
 				$nameParts 				= self::getStringParts($name);
 				$fileType 				= array_pop($nameParts);
 				
-				# if($name == 'index.md' || $fileType !== 'md' ) continue;
-				if($name == 'index.md' || $name == 'index.txt' ) continue;
-													
+				if($fileType == 'md')
+				{
+					$status = 'published';
+				}
+				elseif($fileType == 'txt')
+				{
+					$status = 'unpublished';
+				}
+				else
+				{
+					$fileType = 'txt';
+					$status = 'modified';
+				}
+
 				$item->originalName 	= $name;
 				$item->elementType		= 'file';
+				$item->status 			= $status;
 				$item->fileType			= $fileType;
 				$item->order 			= count($nameParts) > 1 ? array_shift($nameParts) : NULL;
 				$item->name 			= implode(" ",$nameParts);

+ 1 - 0
system/Routes/Api.php

@@ -16,6 +16,7 @@ $app->put('/api/v1/article', ContentApiController::class . ':updateArticle')->se
 $app->delete('/api/v1/article', ContentApiController::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
 $app->post('/api/v1/article/sort', ContentApiController::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
 $app->post('/api/v1/basefolder', ContentApiController::class . ':createBaseFolder')->setName('api.basefolder.create')->add(new RestrictApiAccess($container['router']));
+$app->get('/api/v1/navigation', ContentApiController::class . ':getNavigation')->setName('api.navigation.get')->add(new RestrictApiAccess($container['router']));
 
 $app->post('/api/v1/block', ContentApiController::class . ':addBlock')->setName('api.block.add')->add(new RestrictApiAccess($container['router']));
 $app->put('/api/v1/block', ContentApiController::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));

+ 36 - 15
system/Settings.php

@@ -14,17 +14,12 @@ class Settings
 		if($userSettings)
 		{
 			$settings 			= array_merge($defaultSettings, $userSettings);
-			$settings['setup'] 	= false; 
 		}
-		
-		$settings['images']		= isset($userSettings['images']) ? array_merge($defaultSettings['images'], $userSettings['images']) : $defaultSettings['images'];
-		$settings['themePath'] 	= $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
-		$settings['version']	= $defaultSettings['version'];
-		
+				
 		return array('settings' => $settings);
 	}
 	
-	private static function getDefaultSettings()
+	public static function getDefaultSettings()
 	{
 		$rootPath = __DIR__ . DIRECTORY_SEPARATOR .  '..' . DIRECTORY_SEPARATOR;
 		
@@ -75,28 +70,54 @@ class Settings
 		return $objectSettings;
 	}
 	
-	public static function createSettings($settings)
-	{		
+	public static function createSettings()
+	{
 		$yaml = new Models\WriteYaml();
 		
-		/* write settings to yaml */
-		if($yaml->updateYaml('settings', 'settings.yaml', $settings))
-		{ 
+		# create initial settings file with only setup false
+		if($yaml->updateYaml('settings', 'settings.yaml', array('setup' => false)))
+		{
 			return true; 
 		}
 		return false;
 	}
-	
+
 	public static function updateSettings($settings)
 	{
+		# only allow if usersettings already exists (setup has been done)
 		$userSettings 	= self::getUserSettings();
 		
 		if($userSettings)
 		{
-			$yaml 		= new Models\WriteYaml();
-			$settings 	= array_merge($userSettings, $settings);
+			# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)			
+			$allowedUserSettings = ['displayErrorDetails' => false,
+									'title' => false,
+									'copyright' => false,
+									'language' => false,
+									'startpage' => false,
+									'author' => false,
+									'year' => false,
+									'theme' => false,
+									'editor' => false,
+									'setup' => false,
+									'welcome' => false,
+									'images' => false,
+									'plugins' => false,
+									'themes' => false,
+									'latestVersion' => false 
+								];
+
+			# cleanup the existing usersettings
+			$userSettings = array_intersect_key($userSettings, $allowedUserSettings);
+
+			# cleanup the new settings passed as an argument
+			$settings 	= array_intersect_key($settings, $allowedUserSettings);
 			
+			# merge usersettings with new settings
+			$settings 	= array_merge($userSettings, $settings);
+
 			/* write settings to yaml */
+			$yaml = new Models\WriteYaml();
 			$yaml->updateYaml('settings', 'settings.yaml', $settings);					
 		}
 	}

+ 1 - 1
system/author/auth/welcome.twig

@@ -11,7 +11,7 @@
 				<h1>Hurra!</h1>
 				<p>Your account has been created and you are logged in now.</p>
 				<p><strong>Next step:</strong> Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.</p>
-				<p><strong>New:</strong> Table of content (TOC) are nice and helpful for long content pages and now the table of content will magically update while you write your page in the visual editor. Fancy stuff!!</p>
+				<p><strong>New:</strong> Write beautiful math formulas with markdown with direct preview in the visual editor. We completely refactored it!!</p>
 				<p><strong>Get help:</strong> If you have any questions, please consult the <a target="_blank" href="https://typemill.net/typemill"><i class="icon-link-ext"></i> docs</a> or open a new issue on <a target="_blank" href="https://github.com/typemill/typemill"><i class="icon-link-ext"></i> github</a>.</p>
 			</div>
 			<a class="button" href="{{ path_for('settings.show') }}">Configure your website</a>

+ 30 - 10
system/author/css/style.css

@@ -223,14 +223,19 @@ li.menu-item{
 	position: absolute;
 	width: 4px;
 	height: 100%;
+	max-height: 32px;
 	left: -10px;
 	border-top: 1px solid #f9f8f6; 
 	border-bottom: 1px solid #f9f8f6;
 }
-.status.md{
+.status.published{
 	background:#66b0a3;
 }
-.status.txt{
+.status.modified{
+/*	background: #FFD700; */
+	background: #FFA500;
+}
+.status.unpublished{
 	background:#cc4146;
 }
 .navi-item i.icon-resize-full-alt,
@@ -283,15 +288,17 @@ span.level-5{ padding-left: 70px; }
 	height: 0px;
 	overflow: hidden;
 }
-a.addNaviLink, a.addNaviLink:link, a.addNaviLink:visited{
+.addNaviItem a, .addNaviItem a:link, .addNaviItem a:visited{
 	padding: 3px 0;
 	margin: 0;
 	width: auto;
 	background: transparent;
 	color: #e0474c;
 }
-a.addNaviLink:focus, a.addNaviLink:hover, a.addNaviLink:active{
+.addNaviItem a:focus,.addNaviItem a:hover,.addNaviItem a:active{
 	text-decoration: underline;
+	background: transparent;
+	color: #e0474c;	
 }
 .addNaviForm input{
 	min-height: 0px;
@@ -357,7 +364,9 @@ footer{
 	text-align: center;
 	padding: 20px 0;
 }
-
+.math{
+	white-space: pre;
+}
 /********************
 *  	SETUP FORM 	    *
 ********************/
@@ -1988,18 +1997,25 @@ hr{
 	padding-left: 25px;	
 }
 .blox a, .blox a:link, .blox a:visited,
-footer a, footer a:link, footer a:visited,
-.setupContent a, .setupContent a:link, .setupContent a:visited
+footer a, footer a:link, footer a:visited
 { 
 	text-decoration: none; 
 	color: #e0474c; 
 }
 .blox a:focus, .blox a:hover, .blox a:active,
-footer a:focus, footer a:hover, footer a:active,
-.setupContent a:focus, .setupContent a:hover, .setupContent a:active
+footer a:focus, footer a:hover, footer a:active
 { 
 	text-decoration: underline;
 }
+.setupContent a, .setupContent a:link, .setupContent a:visited
+{
+	text-decoration: none;
+	color: #444;
+}
+.setupContent a:focus, .setupContent a:hover, .setupContent a:active
+{
+	color: #e0474c; 
+}
 .blox .TOC li:before{ color: #bbb; }
 
 
@@ -2253,7 +2269,11 @@ footer a:focus, footer a:hover, footer a:active,
 	.menu-item a:hover:before, .menu-item a:focus:before, .menu-item a:active:before, .menu-item a.active:before{
 		border-left-color: #e0474c;
 	}
-	
+	.addNaviItem a:focus,.addNaviItem a:hover,.addNaviItem a:active{
+		text-decoration: underline;
+		background: transparent;
+		color: #e0474c;	
+	}	
 	.card .medium{
 		padding: 0px 20px;
 	}

+ 2 - 1
system/author/editor/editor-blox.twig

@@ -44,6 +44,7 @@
 								<button class="format-item" @click.prevent="setData( $event, 'toc-component' )" :data-id="index" :id="'blox-' + index" title="table of contents"><i class="icon-list-alt"></i></button>
 								<button class="format-item" @click.prevent="setData( $event, 'hr-component' )" :data-id="index" :id="'blox-' + index" title="horizontal line"><i class="icon-minus"></i></button>
 								<button class="format-item" @click.prevent="setData( $event, 'code-component' )" :data-id="index" :id="'blox-' + index" title="code block"><i class="icon-code"></i></button>
+								<button class="format-item" @click.prevent="setData( $event, 'math-component' )" :data-id="index" :id="'blox-' + index" title="math block"><i class="icon-math"></i></button>
 							</div>
 						</content-block>
 					</draggable>
@@ -63,7 +64,7 @@
 						<button class="format-item" @click.prevent="setData( $event, 'toc-component' )" data-id="99999" id="blox-99999" title="table of contents"><i class="icon-list-alt"></i></button>
 						<button class="format-item" @click.prevent="setData( $event, 'hr-component' )" data-id="99999" id="blox-99999" title="horizontal line"><i class="icon-minus"></i></button>
 						<button class="format-item" @click.prevent="setData( $event, 'code-component' )" data-id="99999" id="blox-99999" title="code block"><i class="icon-code"></i></button>
-				<!--	<button class="format-item" @click.prevent="setData( $event, 'math-component' )" data-id="99999" id="blox-99999" title="math"><i class="icon-math"></i></button>  -->
+						<button class="format-item" @click.prevent="setData( $event, 'math-component' )" data-id="99999" id="blox-99999" title="math"><i class="icon-math"></i></button>
 					</content-block>
 				</div>
 				

+ 57 - 30
system/author/js/lazy-video.js

@@ -1,34 +1,61 @@
-( function() {
 
-    var youtube = document.querySelectorAll( ".youtube" );
-    
-    for (var i = 0; i < youtube.length; i++)
+let typemillUtilities = {
+
+	setYoutubeItems: function()
 	{
-		var thisyoutube = youtube[i];		
-		thisyoutube.parentNode.classList.add("video-container");
-		
-		var playbutton = document.createElement("button");
-		playbutton.classList.add("play-video");
-		playbutton.value = "Play";
+		this.youtubeItems = document.querySelectorAll( ".youtube" );
+	},
+	addYoutubePlayButtons: function(){
+		if(this.youtubeItems)
+		{
+			for(var i = 0; i < this.youtubeItems.length; i++)
+			{
+				var youtubeItem = this.youtubeItems[i];
+				this.addYoutubePlayButton(youtubeItem);
+			}	
+		}	
+	},
+
+	addYoutubePlayButton: function(element)
+	{
+		console.info(element.parentNode);
+		element.parentNode.classList.add("video-container");
 		
-		thisyoutube.parentNode.appendChild(playbutton);
+		var youtubePlaybutton = document.createElement("button");
+		youtubePlaybutton.classList.add("play-video");
+		youtubePlaybutton.value = "Play";
+
+		element.parentNode.appendChild(youtubePlaybutton);	
+	},
+
+	start: function(){
+		this.setYoutubeItems();
+		this.addYoutubePlayButtons();
+		this.listenToYoutube();
+	},
+
+	listenToYoutube: function(){
+		document.addEventListener('click', function (event) {
+
+			if (event.target.matches('.play-video')) {
+
+				var youtubeID = event.target.parentNode.getElementsByClassName('youtube')[0].id;
+
+				event.preventDefault();
+				event.stopPropagation();
+
+				var iframe = document.createElement( "iframe" );
 		
-		playbutton.addEventListener( "click", function(event)
-		{
-			event.preventDefault();
-			event.stopPropagation();
-			
-			var iframe = document.createElement( "iframe" );
-
-			iframe.setAttribute( "frameborder", "0" );
-			iframe.setAttribute( "allowfullscreen", "" );
-			iframe.setAttribute( "width", "560" );
-			iframe.setAttribute( "height", "315" );
-			iframe.setAttribute( "src", "https://www.youtube.com/embed/" + thisyoutube.id + "?rel=0&showinfo=0&autoplay=1" );
-
-			var videocontainer = thisyoutube.parentNode
-			videocontainer.innerHTML = "";
-			videocontainer.appendChild( iframe );
-		})(thisyoutube);
-    };
-} )();
+				iframe.setAttribute( "frameborder", "0" );
+				iframe.setAttribute( "allowfullscreen", "" );
+				iframe.setAttribute( "width", "560" );
+				iframe.setAttribute( "height", "315" );
+				iframe.setAttribute( "src", "https://www.youtube-nocookie.com/embed/" + youtubeID + "?rel=0&showinfo=0&autoplay=1" );
+	
+				var videocontainer = event.target.parentNode;
+				videocontainer.innerHTML = "";
+				videocontainer.appendChild( iframe );
+			}
+		}, true);	
+	},
+};

+ 138 - 3
system/author/js/vue-blox.js

@@ -158,7 +158,7 @@ const contentComponent = Vue.component('content-block', {
 		},
  		submitBlock: function(){
 			var emptyline = /^\s*$(?:\r\n?|\n)/gm;
-			if(this.componentType == "code-component"){ }
+			if(this.componentType == "code-component" || this.componentType == "math-component"){ }
 			else if(this.componentType == "ulist-component" || this.componentType == "olist-component")
 			{
 				var listend = (this.componentType == "ulist-component") ? '* \n' : '1. \n';
@@ -266,6 +266,8 @@ const contentComponent = Vue.component('content-block', {
 						}
 						else
 						{
+							var thisBlockType = self.$root.$data.blockType;
+
 							self.switchToPreviewMode();
 							
 							if(self.$root.$data.blockId == 99999)
@@ -307,13 +309,26 @@ const contentComponent = Vue.component('content-block', {
 							{
 								self.$root.$data.html.splice(result.toc.id, 1, result.toc);
 							}
+
+							/* check math here */
+							self.$root.checkMath(result.id);
+
+							/* check youtube here */
+							if(thisBlockType == "video-component" || thisBlockType == "image-component")
+							{
+								self.$root.checkVideo(result.id);
+							}
+
+							/* update the navigation and mark navigation item as modified */
+							navi.getNavi();
+
 						}
 					}
 					else if(httpStatus != 200)
 					{
 						self.activatePage();
 						publishController.errors.message = "Sorry, something went wrong. Please refresh the page and try again.";
-					}					
+					}	
 				}, method, url, params);
 			}
 		},
@@ -367,6 +382,9 @@ const contentComponent = Vue.component('content-block', {
 						{
 							self.$root.$data.html.splice(result.toc.id, 1, result.toc);
 						}
+						
+						/* update the navigation and mark navigation item as modified */
+						navi.getNavi();
 					}
 				}
 			}, method, url, params);
@@ -967,6 +985,49 @@ const definitionComponent = Vue.component('definition-component', {
 	},
 })
 
+const mathComponent = Vue.component('math-component', {
+	props: ['compmarkdown', 'disabled'],
+	template: '<div>' + 
+				'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +	
+				'<div class="contenttype"><i class="icon-math"></i></div>' +
+				'<textarea class="mdcontent" ref="markdown" v-model="mathblock" :disabled="disabled" @input="createmarkdown"></textarea>' + 
+				'</div>',
+	data: function(){
+		return {
+			mathblock: ''
+		}
+	},
+	mounted: function(){
+		this.$refs.markdown.focus();
+		if(this.compmarkdown)
+		{
+			var dollarMath = new RegExp(/^\$\$[\S\s]+\$\$$/m);
+			var bracketMath = new RegExp(/^\\\[[\S\s]+\\\]$/m);
+
+			if(dollarMath.test(this.compmarkdown) || bracketMath.test(this.compmarkdown))
+			{
+				var mathExpression = this.compmarkdown.substring(2,this.compmarkdown.length-2);
+				this.mathblock = mathExpression.trim(); 
+			}
+		}
+		this.$nextTick(function () {
+			autosize(document.querySelectorAll('textarea'));
+		});
+	},
+	methods: {
+		createmarkdown: function(event)
+		{
+			this.codeblock = event.target.value;
+			var codeblock = '$$\n' + event.target.value + '\n$$';
+			this.updatemarkdown(codeblock);
+		},
+		updatemarkdown: function(codeblock)
+		{
+			this.$emit('updatedMarkdown', codeblock);
+		},
+	},
+})
+
 const videoComponent = Vue.component('video-component', {
 	props: ['compmarkdown', 'disabled', 'load'],
 	template: '<div class="video dropbox">' +
@@ -1275,6 +1336,7 @@ let editor = new Vue({
 		'olist-component': olistComponent,
 		'table-component': tableComponent,
 		'definition-component': definitionComponent,
+		'math-component': mathComponent,
 	},
 	data: {
 		root: document.getElementById("main").dataset.url,
@@ -1351,6 +1413,24 @@ let editor = new Vue({
 				else
 				{
 					self.markdown = result.data;
+					
+					/* make math plugin working */
+					if (typeof renderMathInElement === "function") { 
+						self.$nextTick(function () {
+							renderMathInElement(document.body);
+						});		
+					}
+
+					/* check for youtube videos */
+					if (typeof typemillUtilities !== "undefined")
+					{
+						setTimeout(function(){ 
+							self.$nextTick(function () 
+							{
+								typemillUtilities.start();
+							});
+						}, 200);
+					}
 				}
 			}
 		}, method, url, params);
@@ -1405,6 +1485,12 @@ let editor = new Vue({
 						
 						publishController.publishDisabled = false;
 						publishController.publishResult = "";
+
+						/* update the navigation and mark navigation item as modified */
+						navi.getNavi();
+
+						/* update the math if plugin is there */
+						self.checkMath(params.new_index+1);
 					}
 				}
 			}, method, url, params);
@@ -1467,7 +1553,13 @@ let editor = new Vue({
 				case "[":
 					if(secondChar == "!" && thirdChar == "[") { return "image-component" } else { return "markdown-component" }
 					break;
-				case "`":
+				case "\\":
+					if(secondChar == "["){ return "math-component" } else { return "markdown-component"; }
+					break;
+				case "$":
+						if(secondChar == "$"){ return "math-component" } else { return "markdown-component"; }
+						break;
+					case "`":
 					if(secondChar == "`" && thirdChar == "`") { return "code-component" } else { return "markdown-component" }
 					break;
 				case "*":
@@ -1479,5 +1571,48 @@ let editor = new Vue({
 					return 'markdown-component';
 			}
 		},
+		checkMath(elementid)
+		{
+				/* make math plugin working */
+				if (typeof renderMathInElement === "function")
+				{
+					self.$nextTick(function () {
+						renderMathInElement(document.getElementById("blox-"+elementid));
+					});
+				}
+				if (typeof MathJax !== false) { 
+					self.$nextTick(function () {
+						MathJax.Hub.Queue(["Typeset",MathJax.Hub,"blox-"+elementid]);
+					});
+				}
+		},
+		initiateVideo()
+		{
+			/* check for youtube videos */
+			if (typeof typemillUtilities !== "undefined")
+			{
+				this.$nextTick(function () {
+						typemillUtilities.start();
+				});
+			}
+		},
+		checkVideo(elementid)
+		{
+			/* check for youtube videos */
+			var element = document.getElementById("blox-"+elementid);
+			if(element && typeof typemillUtilities !== "undefined")
+			{
+				imageElement = element.getElementsByClassName("youtube");
+				if(imageElement[0])
+				{
+					setTimeout(function(){ 
+						self.$nextTick(function () 
+						{
+								typemillUtilities.addYoutubePlayButton(imageElement[0]);
+						});
+					}, 300);
+				}
+			}
+		}
 	}
 });

+ 34 - 1
system/author/js/vue-navi.js

@@ -1,6 +1,6 @@
 const navcomponent = Vue.component('navigation', {
 	template: '#navigation-template',
-	props: ['name', 'newItem', 'parent', 'active', 'filetype', 'elementtype', 'element', 'folder', 'level', 'url', 'root', 'freeze'],
+	props: ['homepage', 'name', 'newItem', 'parent', 'active', 'filetype', 'status', 'elementtype', 'element', 'folder', 'level', 'url', 'root', 'freeze'],
 	data: function () {
 		return {
 			showForm: false,
@@ -175,6 +175,7 @@ let navi = new Vue({
 		return {
 			title: "Navigation",
 			items: JSON.parse(document.getElementById("data-navi").dataset.navi),
+			homepage: JSON.parse(document.getElementById("data-navi").dataset.homepage),
 			editormode: document.getElementById("data-navi").dataset.editormode,
 			root: document.getElementById("main").dataset.url,
 			freeze: false,
@@ -242,6 +243,38 @@ let navi = new Vue({
 					}
 				}
 			}, method, url, newFolder );
+		},
+		getNavi: function()
+		{
+			publishController.errors.message = false;
+
+			var self = this;
+			
+			self.freeze = true;
+			self.errors = {title: false, content: false, message: false};
+
+			var activeItem = document.getElementById("path").value;
+			var url = this.root + '/api/v1/navigation?url=' + activeItem;
+			var method 	= 'GET';
+
+			sendJson(function(response, httpStatus)
+			{
+				if(response)
+				{
+					self.freeze = false;
+					var result = JSON.parse(response);
+					
+					if(result.errors)
+					{
+						publishController.errors.message = result.errors;
+					}
+					if(result.data)
+					{
+						self.items = result.data;
+						self.homepage = result.homepage;						
+					}
+				}
+			}, method, url, activeItem );
 		}
 	}
 })

+ 3 - 0
system/author/js/vue-publishcontroller.js

@@ -72,6 +72,7 @@ let publishController = new Vue({
 						self.publishStatus = false;
 						self.publishLabel = "online";
 						self.publishLabelMobile = "ON";
+						navi.getNavi();
 					}
 				}
 				else if(httpStatus != 200)
@@ -121,6 +122,7 @@ let publishController = new Vue({
 					else
 					{
 						self.draftResult = 'success';
+						navi.getNavi();
 					}
 				}
 				else if(httpStatus != 200)
@@ -177,6 +179,7 @@ let publishController = new Vue({
 						self.publishLabel = "offline";
 						self.publishLabelMobile = "OFF";
 						self.publishDisabled = false;
+						navi.getNavi();
 					}
 				}
 			}, method, url, this.form );

+ 6 - 0
system/author/layouts/layoutBlox.twig

@@ -21,6 +21,9 @@
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20190517" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" />
+
+		{{ assets.renderCSS() }}
+
 	</head>
 	<body>	
 		<header class="main-header">
@@ -45,5 +48,8 @@
 		<script src="{{ base_url }}/system/author/js/vue-blox.js?20190517"></script>
 		<script src="{{ base_url }}/system/author/js/vue-navi.js?20190517"></script>
 		<script src="{{ base_url }}/system/author/js/lazy-video.js?20190517"></script>
+
+		{{ assets.renderJS() }}
+
 	</body>
 </html>

+ 6 - 5
system/author/partials/editorNavi.twig

@@ -1,14 +1,15 @@
 <nav id="sidebar-menu" class="sidebar-menu--content">
-	<div id="data-navi" data-navi='{{ navigation|json_encode() }}' data-editormode="{{settings.editor}}"></div>
+	<div id="data-navi" data-navi='{{ navigation|json_encode() }}' data-homepage='{{ homepage|json_encode() }}' data-editormode="{{settings.editor}}"></div>
 	<div id="mobile-menu" class="menu-action">Menu <span class="button-arrow"></span></div>
 	<div id="navi" class="content-navi" v-model="freeze" v-cloak>
 		<div class="navi-list">
 			<div class="navi-item folder">
-				<a href="{{ base_url }}/tm/content/{{ settings.editor }}"><i class="icon-home"></i><span class="level-1">Homepage</span></a>
+				<div class="status" :class="homepage.status"></div>
+				<a href="{{ base_url }}/tm/content/{{ settings.editor }}" :class="homepage.active"><i class="icon-home"></i><span class="level-1">Homepage</span></a>
 			</div>
 		</div>
 		<draggable :element="'ul'" class="navi-list" :list="items" @start="onStart" @end="onEnd" :options="{group:{ name:'folder'}, animation: 150, 'disabled': freeze }">
-			<navigation ref="draggit" v-for="item in items" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :root="root" :url="item.urlRelWoF" v-bind:id="item.keyPath" :key="item.keyPath" :elementtype="item.elementType" :filetype="item.fileType" :folder="item.folderContent"></navigation>
+			<navigation ref="draggit" v-for="item in items" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :root="root" :url="item.urlRelWoF" v-bind:id="item.keyPath" :key="item.keyPath" :elementtype="item.elementType" :filetype="item.fileType" :status="item.status" :folder="item.folderContent"></navigation>
 		</draggable>
 		<ul class="navi-list addBaseFolder">
 			<li class="navi-item file">
@@ -25,10 +26,10 @@
 {% verbatim %}
 	<template id="navigation-template">
 		<li class="navi-item" :class="elementtype">
-			<div class="status" :class="filetype"></div>
+			<div class="status" :class="status"></div>
 			<a v-bind:href="getUrl(root, url)" :class="checkActive(active,parent)"><i :class="getIcon(elementtype, filetype)"></i><span :class="getLevel(level)">{{ name }}</span><i class="icon-move"></i></a>
 			<draggable v-if="folder" :element="'ul'" class="navi-list" :list="folder" :move="checkMove" @start="onStart" @end="onEnd" :options="{group:{ name:'file'}, animation: 150, 'disabled': freeze }">
-				<navigation ref="draggit" v-for="item in folder" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :url="item.urlRelWoF" :root="root" v-bind:id="item.keyPath" :key="item.keyPath" :filetype="item.fileType" :elementtype="item.elementType" :folder="item.folderContent"></navigation>
+				<navigation ref="draggit" v-for="item in folder" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :url="item.urlRelWoF" :root="root" v-bind:id="item.keyPath" :key="item.keyPath" :filetype="item.fileType" :status="item.status" :elementtype="item.elementType" :folder="item.folderContent"></navigation>
 			</draggable>
 			<ul v-if="folder" class="navi-list"><li class="navi-item file"><i class="icon-plus"></i><span :class="getLevel(level + '.0')" class="addNaviItem"><a class="addNaviLink" href="#" @click.prevent="toggleForm">add item</a></span><transition name="fade"><div v-if="showForm" class="addNaviForm"><input v-model="newItem"><button class="b-left" @click="addFile('file')">add file</button><button class="b-right" @click="addFile('folder')">add folder</button></div></transition></li></ul>
 		</li>

+ 5 - 5
system/system.php

@@ -50,13 +50,13 @@ $container = $app->getContainer();
 * LOAD & UPDATE PLUGINS *
 ************************/
 
-$plugins 					= new Typemill\Plugins();
-$pluginNames				= $plugins->load();
+$plugins 				= new Typemill\Plugins();
+$pluginNames		= $plugins->load();
 $pluginSettings = $routes = $middleware	= array();
 
 foreach($pluginNames as $pluginName)
 {
-	$className			= $pluginName['className'];
+	$className	= $pluginName['className'];
 	$name				= $pluginName['name'];
 		
 	# check if plugin is in the settings already
@@ -81,13 +81,13 @@ foreach($pluginNames as $pluginName)
 	if($pluginSettings[$name]['active'])
 	{
 		$routes 			= $plugins->getNewRoutes($className, $routes);
-		$middleware			= $plugins->getNewMiddleware($className, $middleware);
+		$middleware		= $plugins->getNewMiddleware($className, $middleware);
 		
 		$dispatcher->addSubscriber(new $className($container));
 	}
 }
 
-# if plugins in settings are not empty, then a plugin has been removed
+# if plugins in original settings are not empty now, then a plugin has been removed
 if(!empty($settings['settings']['plugins'])){ $refreshSettings = true; }
 
 # update the settings in all cases

+ 3 - 1
themes/typemill/css/style.css

@@ -494,7 +494,9 @@ pre{
 	max-width: 100%;
 	overflow-x: auto;
 }
-
+.math{
+	white-space: pre;
+}
 table{
 	width: 100%;
 	border-collapse: collapse;

+ 2 - 2
themes/typemill/partials/layout.twig

@@ -58,8 +58,8 @@
 		{% block javascripts %}
 
 			<script src="{{ base_url }}/themes/typemill/js/script.js"></script>
-			<script src="{{ base_url }}/system/author/js/lazy-video.js"></script>
-
+			<script src="{{ base_url }}/system/author/js/lazy-video.js?20190602"></script>
+			<script>typemillUtilities.start();</script>
 			{{ assets.renderJS() }}
 		
 		{% endblock %}		

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است