mirror of
https://github.com/typemill/typemill.git
synced 2025-10-17 23:56:09 +02:00
Version 1.2.15: Beautiful Math Refactored
This commit is contained in:
@@ -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 */
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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 ));
|
||||
}
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -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'));
|
||||
}
|
||||
|
@@ -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))
|
||||
|
||||
$element = parent::inlineFootnoteMarker($Excerpt);
|
||||
|
||||
if ( ! isset($element))
|
||||
{
|
||||
$name = $matches[1];
|
||||
|
||||
if ( ! isset($this->DefinitionData['Footnote'][$name]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->DefinitionData['Footnote'][$name]['count'] ++;
|
||||
|
||||
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
|
||||
{
|
||||
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
|
||||
}
|
||||
|
||||
$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'],
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'extent' => strlen($matches[0]),
|
||||
'element' => $Element,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$href = $element['element']['element']['attributes']['href'];
|
||||
|
||||
$element['element']['element']['attributes']['href'] = $this->relurl . $href;
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Fenced MathJax
|
||||
protected function blockFencedMathJaxLaTeX($Line)
|
||||
{
|
||||
if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
|
||||
),
|
||||
);
|
||||
if (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;
|
||||
}
|
||||
elseif (preg_match('/^(?<!\\\\)(\$\$)(?!.)$/', $Line['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['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;
|
||||
}
|
||||
elseif ($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;
|
||||
}
|
||||
$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
system/Extensions/ParsedownMath.php
Normal file
165
system/Extensions/ParsedownMath.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
@@ -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']));
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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";
|
||||
|
||||
thisyoutube.parentNode.appendChild(playbutton);
|
||||
|
||||
playbutton.addEventListener( "click", function(event)
|
||||
this.youtubeItems = document.querySelectorAll( ".youtube" );
|
||||
},
|
||||
addYoutubePlayButtons: function(){
|
||||
if(this.youtubeItems)
|
||||
{
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var iframe = document.createElement( "iframe" );
|
||||
for(var i = 0; i < this.youtubeItems.length; i++)
|
||||
{
|
||||
var youtubeItem = this.youtubeItems[i];
|
||||
this.addYoutubePlayButton(youtubeItem);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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" );
|
||||
addYoutubePlayButton: function(element)
|
||||
{
|
||||
console.info(element.parentNode);
|
||||
element.parentNode.classList.add("video-container");
|
||||
|
||||
var youtubePlaybutton = document.createElement("button");
|
||||
youtubePlaybutton.classList.add("play-video");
|
||||
youtubePlaybutton.value = "Play";
|
||||
|
||||
var videocontainer = thisyoutube.parentNode
|
||||
videocontainer.innerHTML = "";
|
||||
videocontainer.appendChild( iframe );
|
||||
})(thisyoutube);
|
||||
};
|
||||
} )();
|
||||
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" );
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@@ -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 );
|
||||
}
|
||||
}
|
||||
})
|
@@ -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 );
|
||||
|
@@ -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>
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user