1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-05 13:47:37 +02:00

Merge branch 'version135'

This commit is contained in:
trendschau
2020-04-20 19:25:01 +02:00
33 changed files with 725 additions and 425 deletions

View File

@@ -7,6 +7,7 @@ use Slim\Http\Response;
use Typemill\Models\Folder;
use Typemill\Models\Write;
use Typemill\Models\WriteYaml;
use Typemill\Models\WriteMeta;
use Typemill\Extensions\ParsedownExtension;
use Typemill\Events\OnPagePublished;
use Typemill\Events\OnPageUnpublished;
@@ -77,10 +78,15 @@ class ArticleApiController extends ContentController
# update the public structure
$this->setStructure($draft = false, $cache = false);
# dispatch event
$this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($this->item));
# complete the page meta if title or description not set
$writeMeta = new WriteMeta();
$meta = $writeMeta->completePageMeta($this->content, $this->settings, $this->item);
return $response->withJson(['success'], 200);
# dispatch event
$page = ['content' => $this->content, 'meta' => $meta, 'item' => $this->item];
$this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page));
return $response->withJson(['success' => true, 'meta' => $meta], 200);
}
else
{
@@ -260,7 +266,7 @@ class ArticleApiController extends ContentController
}
else
{
return $response->withJson(array('data' => $this->structure, 'errors' => $this->errors), 404);
return $response->withJson(array('data' => $this->structure, 'errors' => $this->errors), 422);
}
}
@@ -465,13 +471,12 @@ class ArticleApiController extends ContentController
# create the url for the item
$urlWoF = $folder->urlRelWoF . '/' . $slug;
# add the navigation name to the item htmlspecialchars needed for frensh language
# add the navigation name to the item htmlspecialchars needed for french language
$extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
# store the extended structure
$write->updateYaml('cache', 'structure-extended.yaml', $extended);
# update the structure for editor
$this->setStructure($draft = true, $cache = false);
@@ -482,7 +487,6 @@ class ArticleApiController extends ContentController
return $response->withJson(array('posts' => $folder, $this->structure, 'errors' => false, 'url' => $url));
}
public function createArticle(Request $request, Response $response, $args)
{
@@ -546,14 +550,10 @@ class ArticleApiController extends ContentController
if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
# add prefix number to the name
# $namePath = $index > 9 ? $index . '-' . $name : '0' . $index . '-' . $name;
$namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug;
$folderPath = 'content' . $folder->path;
# $title = implode(" ", $nameParts);
# create default content
# $content = json_encode(['# ' . $title, 'Content']);
$content = json_encode(['# ' . $name, 'Content']);
if($this->params['type'] == 'file')
@@ -576,21 +576,18 @@ class ArticleApiController extends ContentController
}
# get extended structure
$extended = $write->getYaml('cache', 'structure-extended.yaml');
# create the url for the item
$urlWoF = $folder->urlRelWoF . '/' . $slug;
# add the navigation name to the item htmlspecialchars needed for frensh language
# add the navigation name to the item htmlspecialchars needed for french language
$extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
# store the extended structure
$write->updateYaml('cache', 'structure-extended.yaml', $extended);
# update the structure for editor
$this->setStructure($draft = true, $cache = false);
@@ -643,7 +640,7 @@ class ArticleApiController extends ContentController
# check, if the same name as new item, then return an error
if($item->slug == $slug)
{
return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 422);
}
if(!$write->moveElement($item, '', $index))
@@ -653,7 +650,7 @@ class ArticleApiController extends ContentController
$index++;
}
if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422); }
# add prefix number to the name
$namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug;
@@ -667,14 +664,14 @@ class ArticleApiController extends ContentController
{
if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
{
return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422);
}
}
elseif($this->params['type'] == 'folder')
{
if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
{
return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422);
}
$write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
@@ -695,7 +692,6 @@ class ArticleApiController extends ContentController
# store the extended structure
$write->updateYaml('cache', 'structure-extended.yaml', $extended);
# update the structure for editor
$this->setStructure($draft = true, $cache = false);

View File

@@ -24,7 +24,6 @@ class AuthController extends Controller
}
}
/**
* show login form
*

View File

@@ -769,28 +769,29 @@ class BlockApiController extends ContentController
$imageData = @file_get_contents($videoURL0, 0, $ctx);
if($imageData === false)
{
return $response->withJson(array('errors' => 'could not get the video image'));
return $response->withJson(['errors' => ['message' => 'We did not find that video or could not get a preview image.']], 500);
}
}
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
$imageProcessor = new ProcessImage($this->settings['images']);
if(!$imageProcessor->checkFolders())
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
$desiredSizes = $this->settings['images'];
$desiredSizes['live'] = ['width' => 560, 'height' => 315];
$imageProcessor = new ProcessImage($desiredSizes);
if(!$imageProcessor->checkFolders('images'))
{
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
}
$tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes);
if(!$tmpImage)
if(!$imageProcessor->createImage($imageData64, 'youtube-' . $videoID, $desiredSizes, $overwrite = true))
{
return $response->withJson(array('errors' => 'could not create temporary image'));
return $response->withJson(['errors' => ['message' => 'We could not create the image.']], 500);
}
$imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID);
$imageUrl = $imageProcessor->publishImage();
if($imageUrl)
{
$this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}';
$request = $request->withParsedBody($this->params);

View File

@@ -60,6 +60,7 @@ abstract class ContentController
$this->structureDraftName = 'structure-draft.txt';
}
# admin ui rendering
protected function render($response, $route, $data)
{
if(isset($_SESSION['old']))
@@ -68,7 +69,7 @@ abstract class ContentController
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
@@ -376,7 +377,7 @@ abstract class ContentController
if(file_exists($path))
{
$files = array_diff(scandir($path), array('.', '..'));
# check if there are published pages or folders inside, then stop the operation
foreach ($files as $file)
{
@@ -385,9 +386,9 @@ abstract class ContentController
$this->errors = ['message' => 'Please delete the sub-folder first.'];
}
if(substr($file, -3) == '.md' )
if(substr($file, -3) == '.md' && $file != 'index.md')
{
$this->errors = ['message' => 'Please unpublish all pages in the folder first.'];
$this->errors = ['message' => 'Please unpublish all pages in the folder first.'];
}
}

View File

@@ -18,6 +18,7 @@ abstract class Controller
$this->c = $c;
}
# frontend rendering
protected function render($response, $route, $data)
{
# why commented this out??
@@ -29,8 +30,6 @@ abstract class Controller
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
@@ -40,6 +39,8 @@ abstract class Controller
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
$response = $response->withAddedHeader('X-Powered-By', 'Typemill');
return $this->c->view->render($response, $route, $data);
}

View File

@@ -320,19 +320,34 @@ class MediaApiController extends ContentController
return $response->withJson(array('errors' => 'could not store the preview image'));
}
# https://www.sitepoint.com/mime-types-complete-list/
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
private function getAllowedMtypes()
{
return array(
'application/zip',
'application/gzip',
'application/x-gzip',
'application/x-compressed',
'application/x-zip-compressed',
'application/vnd.rar',
'application/x-7z-compressed',
'application/x-visio',
'application/vnd.visio',
'application/excel',
'application/x-excel',
'application/x-msexcel',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.ms-word.document.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/powerpoint',
'application/mspowerpoint',
'application/x-mspowerpoint',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/x-project',
'application/vnd.ms-project',
'application/vnd.apple.keynote',
'application/vnd.apple.mpegurl',
'application/vnd.apple.numbers',
@@ -340,17 +355,31 @@ class MediaApiController extends ContentController
'application/vnd.amazon.mobi8-ebook',
'application/epub+zip',
'application/pdf',
'application/x-latex',
'image/png',
'image/jpeg',
'image/gif',
'image/tiff',
'image/x-tiff',
'image/svg+xml',
'image/x-icon',
'text/plain',
'application/plain',
'text/richtext',
'text/vnd.rn-realtext',
'application/rtf',
'application/x-rtf',
'font/*',
'audio/mpeg',
'audio/mp4',
'audio/ogg',
'audio/3gpp',
'audio/3gpp2',
'video/mpeg',
'video/mp4',
'video/ogg',
'video/3gpp',
'video/3gpp2',
);
}
}

View File

@@ -5,6 +5,7 @@ namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Typemill\Models\WriteYaml;
use Typemill\Models\WriteMeta;
use Typemill\Models\Folder;
class MetaApiController extends ContentController
@@ -24,6 +25,7 @@ class MetaApiController extends ContentController
$metatabs = $writeYaml->getYaml('system' . DIRECTORY_SEPARATOR . 'author', 'metatabs.yaml');
# add radio buttons to choose posts or pages for folder.
if($folder)
{
$metatabs['meta']['fields']['contains'] = [
@@ -70,22 +72,22 @@ class MetaApiController extends ContentController
# set item
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
$writeYaml = new writeYaml();
$writeMeta = new writeMeta();
$pagemeta = $writeYaml->getPageMeta($this->settings, $this->item);
$pagemeta = $writeMeta->getPageMeta($this->settings, $this->item);
if(!$pagemeta)
{
# set the status for published and drafted
$this->setPublishStatus();
# set path
$this->setItemPath($this->item->fileType);
# read content from file
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
$pagemeta = $writeYaml->getPageMetaDefaults($this->content, $this->settings, $this->item);
$pagemeta = $writeMeta->getPageMetaBlank($this->content, $this->settings, $this->item);
}
# if item is a folder
@@ -118,7 +120,7 @@ class MetaApiController extends ContentController
}
# store the metascheme in cache for frontend
$writeYaml->updateYaml('cache', 'metatabs.yaml', $metascheme);
$writeMeta->updateYaml('cache', 'metatabs.yaml', $metascheme);
return $response->withJson(array('metadata' => $metadata, 'metadefinitions' => $metadefinitions, 'item' => $this->item, 'errors' => false));
}
@@ -187,13 +189,13 @@ class MetaApiController extends ContentController
# return validation errors
if($errors){ return $response->withJson(array('errors' => $errors),422); }
$writeYaml = new writeYaml();
$writeMeta = new writeMeta();
# get existing metadata for page
$metaPage = $writeYaml->getYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml');
$metaPage = $writeMeta->getYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml');
# get extended structure
$extended = $writeYaml->getYaml('cache', 'structure-extended.yaml');
$extended = $writeMeta->getYaml('cache', 'structure-extended.yaml');
# flag for changed structure
$structure = false;
@@ -218,7 +220,7 @@ class MetaApiController extends ContentController
$pathWithoutFile = str_replace($this->item->originalName, "", $this->item->path);
$newPathWithoutType = $pathWithoutFile . $datetime . '-' . $this->item->slug;
$writeYaml->renamePost($this->item->pathWithoutType, $newPathWithoutType);
$writeMeta->renamePost($this->item->pathWithoutType, $newPathWithoutType);
# recreate the draft structure
$this->setStructure($draft = true, $cache = false);
@@ -235,11 +237,11 @@ class MetaApiController extends ContentController
if($metaInput['contains'] == "posts")
{
$writeYaml->transformPagesToPosts($this->item);
$writeMeta->transformPagesToPosts($this->item);
}
if($metaInput['contains'] == "pages")
{
$writeYaml->transformPostsToPages($this->item);
$writeMeta->transformPostsToPages($this->item);
}
}
@@ -273,12 +275,12 @@ class MetaApiController extends ContentController
$meta[$tab] = $metaInput;
# store the metadata
$writeYaml->updateYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml', $meta);
$writeMeta->updateYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml', $meta);
if($structure)
{
# store the extended file
$writeYaml->updateYaml('cache', 'structure-extended.yaml', $extended);
$writeMeta->updateYaml('cache', 'structure-extended.yaml', $extended);
# recreate the draft structure
$this->setStructure($draft = true, $cache = false);
@@ -309,6 +311,4 @@ class MetaApiController extends ContentController
}
return true;
}
}
# check models -> writeYaml for getPageMeta and getPageMetaDefaults.
}

View File

@@ -6,6 +6,7 @@ use Typemill\Models\Folder;
use Typemill\Models\WriteCache;
use Typemill\Models\WriteSitemap;
use Typemill\Models\WriteYaml;
use Typemill\Models\WriteMeta;
use \Symfony\Component\Yaml\Yaml;
use Typemill\Models\VersionCheck;
use Typemill\Models\Helpers;
@@ -30,7 +31,6 @@ class PageController extends Controller
$item = false;
$home = false;
$breadcrumb = false;
$description = '';
$settings = $this->c->get('settings');
$pathToContent = $settings['rootPath'] . $settings['contentFolder'];
$cache = new WriteCache();
@@ -61,10 +61,6 @@ class PageController extends Controller
/* update sitemap */
$sitemap = new WriteSitemap();
$sitemap->updateSitemap('cache', 'sitemap.xml', 'lastSitemap.txt', $structure, $uri->getBaseUrl());
/* check and update the typemill-version in the user settings */
# this version check is not needed
# $this->updateVersion($uri->getBaseUrl());
}
}
@@ -89,7 +85,7 @@ class PageController extends Controller
if(empty($args))
{
$home = true;
$item = Folder::getItemForUrl($structure, $uri->getBasePath(), $uri->getBasePath());
$item = Folder::getItemForUrl($navigation, $uri->getBasePath(), $uri->getBasePath());
}
else
{
@@ -110,10 +106,10 @@ class PageController extends Controller
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
# set pages active for navigation again
Folder::getBreadcrumb($navigation, $item->keyPathArray);
Folder::getBreadcrumb($structure, $item->keyPathArray);
/* add the paging to the item */
$item = Folder::getPagingForItem($structure, $item);
$item = Folder::getPagingForItem($navigation, $item);
}
# dispatch the item
@@ -126,6 +122,9 @@ class PageController extends Controller
if($item->elementType == 'folder')
{
$filePath = $filePath . DIRECTORY_SEPARATOR . 'index.md';
# use navigation instead of structure to get
$item = Folder::getItemForUrl($navigation, $urlRel, $uri->getBasePath());
}
# read the content of the file
@@ -135,13 +134,10 @@ class PageController extends Controller
$this->c->dispatcher->dispatch('onOriginalLoaded', new OnOriginalLoaded($contentMD));
# get meta-Information
$writeYaml = new WriteYaml();
$metatabs = $writeYaml->getPageMeta($settings, $item);
$writeMeta = new WriteMeta();
if(!$metatabs)
{
$metatabs = $writeYaml->getPageMetaDefaults($contentMD, $settings, $item);
}
# makes sure that you always have the full meta with title, description and all the rest.
$metatabs = $writeMeta->completePageMeta($contentMD, $settings, $item);
# dispatch meta
$metatabs = $this->c->dispatcher->dispatch('onMetaLoaded', new OnMetaLoaded($metatabs))->getData();
@@ -149,20 +145,20 @@ class PageController extends Controller
# dispatch content
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
$itemUrl = isset($item->urlRel) ? $item->urlRel : false;
/* initialize parsedown */
$parsedown = new ParsedownExtension();
$parsedown = new ParsedownExtension($settings['headlineanchors']);
/* set safe mode to escape javascript and html in markdown */
$parsedown->setSafeMode(true);
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($contentMD);
$contentArray = $parsedown->text($contentMD, $itemUrl);
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
/* 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 */
$contentHTML = $parsedown->markup($contentArray, $itemUrl);
@@ -172,25 +168,7 @@ class PageController extends Controller
$contentParts = explode("</h1>", $contentHTML);
$title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $settings['title'];
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
# if there is not meta description
if(!isset($metatabs['meta']['description']) or !$metatabs['meta']['description'])
{
# create excerpt from html
$excerpt = substr($contentHTML,0,500);
# create description from excerpt
$description = isset($excerpt) ? strip_tags($excerpt) : false;
if($description)
{
$description = trim(preg_replace('/\s+/', ' ', $description));
$description = substr($description, 0, 300);
$lastSpace = strrpos($description, ' ');
$metatabs['meta']['description'] = substr($description, 0, $lastSpace);
}
}
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
/* get url and alt-tag for first image, if exists */
if($firstImage)
@@ -208,7 +186,7 @@ class PageController extends Controller
$route = empty($args) && isset($settings['themes'][$theme]['cover']) ? '/cover.twig' : '/index.twig';
# check if there is a custom theme css
$customcss = $writeYaml->checkFile('cache', $theme . '-custom.css');
$customcss = $writeMeta->checkFile('cache', $theme . '-custom.css');
if($customcss)
{
$this->c->assets->addCSS($base_url . '/cache/' . $theme . '-custom.css');
@@ -262,10 +240,10 @@ class PageController extends Controller
$yaml = new writeYaml();
$extended = $yaml->getYaml('cache', 'structure-extended.yaml');
/* create an array of object with the whole content of the folder */
# create an array of object with the whole content of the folder
$structure = Folder::getFolderContentDetails($structure, $extended, $uri->getBaseUrl(), $uri->getBasePath());
/* cache structure */
# cache structure
$cache->updateCache('cache', 'structure.txt', 'lastCache.txt', $structure);
if($extended && $this->containsHiddenPages($extended))
@@ -282,7 +260,8 @@ class PageController extends Controller
$cache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt');
}
return $structure;
# load and return the cached structure, because might be manipulated with navigation....
return $this->getCachedStructure($cache);
}
protected function containsHiddenPages($extended)

View File

@@ -57,15 +57,16 @@ class SettingsController extends Controller
{
/* make sure only allowed fields are stored */
$newSettings = array(
'title' => $newSettings['title'],
'author' => $newSettings['author'],
'copyright' => $newSettings['copyright'],
'year' => $newSettings['year'],
'language' => $newSettings['language'],
'editor' => $newSettings['editor'],
'formats' => $newSettings['formats'],
'title' => $newSettings['title'],
'author' => $newSettings['author'],
'copyright' => $newSettings['copyright'],
'year' => $newSettings['year'],
'language' => $newSettings['language'],
'editor' => $newSettings['editor'],
'formats' => $newSettings['formats'],
'headlineanchors' => isset($newSettings['headlineanchors']) ? $newSettings['headlineanchors'] : null,
);
# https://www.slimframework.com/docs/v3/cookbook/uploading-files.html;
$copyright = $this->getCopyright();

View File

@@ -52,7 +52,7 @@ class SetupController extends Controller
$setuperrors = empty($systemcheck) ? false : 'Some system requirements for Typemill are missing.';
$systemcheck = empty($systemcheck) ? false : $systemcheck;
return $this->render($response, 'auth/setup.twig', array( 'messages' => $setuperror, 'systemcheck' => $systemcheck ));
return $this->render($response, 'auth/setup.twig', array( 'messages' => $setuperrors, 'systemcheck' => $systemcheck ));
}
public function create($request, $response, $args)

View File

@@ -6,10 +6,13 @@ use \URLify;
class ParsedownExtension extends \ParsedownExtra
{
function __construct()
function __construct($showAnchor = NULL)
{
parent::__construct();
# show anchor next to headline?
$this->showAnchor = $showAnchor;
# math support
$this->BlockTypes['\\'][] = 'Math';
$this->BlockTypes['$'][] = 'Math';
@@ -30,8 +33,10 @@ class ParsedownExtension extends \ParsedownExtra
$this->visualMode = true;
}
public function text($text)
public function text($text, $relurl = null)
{
$this->relurl = $relurl ? $relurl : '';
$Elements = $this->textElements($text);
return $Elements;
@@ -117,20 +122,37 @@ class ParsedownExtension extends \ParsedownExtra
{
return;
}
$text = trim($text, ' ');
$Block = array(
'element' => array(
'name' => 'h' . min(6, $level),
'text' => $text,
'handler' => 'line',
'attributes' => array(
'id' => "$headline"
)
)
);
$Block = array(
'element' => array(
'name' => 'h' . min(6, $level),
'text' => $text,
'handler' => 'line',
'attributes' => array(
'id' => "$headline"
)
)
);
if($this->showAnchor && $level > 1)
{
$Block['element']['elements'] = array(
array(
'name' => 'a',
'attributes' => array(
'href' => $this->relurl . "#" . $headline,
'class' => 'tm-heading-anchor',
),
'text' => '#',
),
array(
'text' => $text,
)
);
}
$this->headlines[] = array('level' => $level, 'name' => $Block['element']['name'], 'attribute' => $Block['element']['attributes']['id'], 'text' => $text);
return $Block;

View File

@@ -2,7 +2,7 @@
namespace Typemill\Extensions;
use Typemill\Models\WriteYaml;
use Typemill\Models\WriteMeta;
class TwigMetaExtension extends \Twig_Extension
{
@@ -15,9 +15,30 @@ class TwigMetaExtension extends \Twig_Extension
public function getMeta($settings, $item)
{
$write = new WriteYaml();
$writeMeta = new WriteMeta();
$meta = $write->getPageMeta($settings, $item);
$meta = $writeMeta->getPageMeta($settings, $item);
if(!$meta OR $meta['meta']['title'] == '' OR $meta['meta']['description'] == '')
{
# create path to the file
$filePath = $settings['rootPath'] . $settings['contentFolder'] . $item->path;
# check if url is a folder and add index.md
if($item->elementType == 'folder')
{
$filePath = $filePath . DIRECTORY_SEPARATOR . 'index.md';
}
if(file_exists($filePath))
{
# get content
$content = file_get_contents($filePath);
# completes title and description or generate default meta values
$meta = $writeMeta->completePageMeta($content, $settings, $item);
}
}
return $meta;
}

View File

@@ -98,7 +98,7 @@ class ProcessAssets
return (count(scandir($dir)) == 2);
}
public function setFileName($originalname, $type, $overwrite = null)
public function setFileName($originalname, $type, $overwrite = NULL)
{
$pathinfo = pathinfo($originalname);
@@ -135,6 +135,11 @@ class ProcessAssets
return $this->filename;
}
public function setExtension($extension)
{
$this->extension = $extension;
}
public function getExtension()
{
return $this->extension;

View File

@@ -5,7 +5,7 @@ use Typemill\Models\Helpers;
class ProcessImage extends ProcessAssets
{
public function createImage(string $image, string $name, array $desiredSizes)
public function createImage(string $image, string $name, array $desiredSizes, $overwrite = NULL)
{
# fix error from jpeg-library
ini_set ('gd.jpeg_ignore_warning', 1);
@@ -15,13 +15,15 @@ class ProcessImage extends ProcessAssets
$this->clearTempFolder();
# set the name of the image
$this->setFileName($name, 'image');
$this->setFileName($name, 'image', $overwrite);
# decode the image from base64-string
$imageDecoded = $this->decodeImage($image);
$imageData = $imageDecoded["image"];
$imageType = $imageDecoded["type"];
$this->setExtension($imageType);
# transform image-stream into image
$image = imagecreatefromstring($imageData);
@@ -38,7 +40,7 @@ class ProcessImage extends ProcessAssets
$tmpname = fopen($this->tmpFolder . $this->getName() . '.' . $imageType . ".txt", "w");
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'original', $imageType);
# temporary store resized images
foreach($resizedImages as $key => $resizedImage)
{
@@ -71,8 +73,8 @@ class ProcessImage extends ProcessAssets
{
$tmpname = str_replace('.txt', '', basename($imagename));
# set extension and sanitize name
$this->setFileName($tmpname, 'image');
# set extension and sanitize name. Overwrite because this has been checked before
$this->setFileName($tmpname, 'image', $overwrite = true);
unlink($imagename);
}
@@ -80,7 +82,7 @@ class ProcessImage extends ProcessAssets
$name = uniqid();
if($this->filename && $this->extension)
{
{
$name = $this->filename;
}
@@ -110,7 +112,7 @@ class ProcessImage extends ProcessAssets
if($success)
{
return true;
# return true;
return 'media/live/' . $name . '.' . $tmpfilename[1];
}
@@ -201,7 +203,9 @@ class ProcessImage extends ProcessAssets
# save resized images in temporary folder
public function saveImage($folder, $image, $name, $type)
{
{
$type = strtolower($type);
if($type == "png")
{
$result = imagepng( $image, $folder . $name . '.png' );
@@ -210,10 +214,14 @@ class ProcessImage extends ProcessAssets
{
$result = imagegif( $image, $folder . $name . '.gif' );
}
elseif($type == "jpg" OR $type == "jpeg")
{
$result = imagejpeg( $image, $folder . $name . '.' . $type );
}
else
{
$result = imagejpeg( $image, $folder . $name . '.jpeg' );
$type = 'jpeg';
# image type not supported
return false;
}
imagedestroy($image);
@@ -339,12 +347,13 @@ class ProcessImage extends ProcessAssets
{
$this->setFileName($filename, 'image', $overwrite = true);
if($this->extension == 'jpeg') $this->extension = 'jpg';
# if($this->extension == 'jpg') $this->extension = 'jpeg';
switch($this->extension)
{
case 'gif': $image = imagecreatefromgif($this->liveFolder . $filename); break;
case 'jpg': $image = imagecreatefromjpeg($this->liveFolder . $filename); break;
case 'jpg' :
case 'jpeg': $image = imagecreatefromjpeg($this->liveFolder . $filename); break;
case 'png': $image = imagecreatefrompng($this->liveFolder . $filename); break;
default: return 'image type not supported';
}
@@ -367,12 +376,13 @@ class ProcessImage extends ProcessAssets
{
$this->setFileName($filename, 'image');
if($this->extension == 'jpeg') $this->extension = 'jpg';
if($this->extension == 'jpg') $this->extension = 'jpeg';
switch($this->extension)
{
case 'gif': $image = imagecreatefromgif($image); break;
case 'jpg': $image = imagecreatefromjpeg($image); break;
case 'jpg' :
case 'jpeg': $image = imagecreatefromjpeg($image); break;
case 'png': $image = imagecreatefrompng($image); break;
default: return 'image type not supported';
}
@@ -383,5 +393,4 @@ class ProcessImage extends ProcessAssets
return $resizedImages;
}
}

348
system/Models/WriteMeta.php Normal file
View File

@@ -0,0 +1,348 @@
<?php
namespace Typemill\Models;
use Typemill\Extensions\ParsedownExtension;
class WriteMeta extends WriteYaml
{
# used by contentApiController (backend) and pageController (frontend) and TwigMetaExtension (list pages)
public function getPageMeta($settings, $item)
{
$meta = $this->getYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml');
if(!$meta)
{
return false;
}
# compare with meta that are in use right now (e.g. changed theme, disabled plugin)
$metascheme = $this->getYaml('cache', 'metatabs.yaml');
if($metascheme)
{
$meta = $this->whitelistMeta($meta,$metascheme);
}
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
return $meta;
}
# cases are rare: updates from old version prior 1.3.4 or if content-files are added manually, e.g. by ftp
public function getPageMetaDefaults($content, $settings, $item)
{
# initialize parsedown extension
$parsedown = new ParsedownExtension();
# if content is not an array, then transform it
if(!is_array($content))
{
# turn markdown into an array of markdown-blocks
$content = $parsedown->markdownToArrayBlocks($content);
}
$title = false;
# delete markdown from title
if(isset($content[0]))
{
$title = trim($content[0], "# ");
}
$description = $this->generateDescription($content, $parsedown, $item);
$author = $settings['author'];
if(isset($_SESSION))
{
if(isset($_SESSION['firstname']) && $_SESSION['firstname'] !='' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
{
$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
}
elseif(isset($_SESSION['user']))
{
$author = $_SESSION['user'];
}
}
# create new meta-file
$meta = [
'meta' => [
'title' => $title,
'description' => $description,
'author' => $author,
'created' => date("Y-m-d"),
'time' => date("H-i-s"),
'navtitle' => $item->name,
]
];
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
return $meta;
}
# used by MetaApiController. Do not set title or description in defaults if page is not published yet
public function getPageMetaBlank($content, $settings, $item)
{
$author = $settings['author'];
if(isset($_SESSION))
{
if(isset($_SESSION['firstname']) && $_SESSION['firstname'] !='' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
{
$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
}
elseif(isset($_SESSION['user']))
{
$author = $_SESSION['user'];
}
}
# create new meta-file
$meta = [
'meta' => [
'title' => '',
'description' => '',
'author' => $author,
'created' => date("Y-m-d"),
'time' => date("H-i-s"),
'navtitle' => $item->name
]
];
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
return $meta;
}
public function getNavtitle($url)
{
# get the extended structure where the navigation title is stored
$extended = $this->getYaml('cache', 'structure-extended.yaml');
if(isset($extended[$url]['navtitle']))
{
return $extended[$url]['navtitle'];
}
return '';
}
# used by articleApiController and pageController to add title and description if an article is published
public function completePageMeta($content, $settings, $item)
{
$meta = $this->getPageMeta($settings, $item);
if(!$meta)
{
return $this->getPageMetaDefaults($content, $settings, $item);
}
$title = (isset($meta['meta']['title']) AND $meta['meta']['title'] !== '') ? true : false;
$description = (isset($meta['meta']['description']) AND $meta['meta']['description'] !== '') ? true : false;
if($title && $description)
{
return $meta;
}
# initialize parsedown extension
$parsedown = new ParsedownExtension();
# if content is not an array, then transform it
if(!is_array($content))
{
# turn markdown into an array of markdown-blocks
$content = $parsedown->markdownToArrayBlocks($content);
}
# delete markdown from title
if(!$title && isset($content[0]))
{
$meta['meta']['title'] = trim($content[0], "# ");
}
if(!$description && isset($content[1]))
{
$meta['meta']['description'] = $this->generateDescription($content, $parsedown, $item);
}
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
return $meta;
}
private function whitelistMeta($meta, $metascheme)
{
# we have only 2 dimensions, so no recursive needed
foreach($meta as $tab => $values)
{
if(!isset($metascheme[$tab]))
{
unset($meta[$tab]);
}
foreach($values as $key => $value)
{
if(!isset($metascheme[$tab][$key]))
{
unset($meta[$tab][$key]);
}
}
}
return $meta;
}
private function addFileTimeToMeta($meta, $item, $settings)
{
$filePath = $settings['contentFolder'] . $item->path;
$fileType = isset($item->fileType) ? $item->fileType : 'md';
# check if url is a folder.
if($item->elementType == 'folder')
{
$filePath = $settings['contentFolder'] . $item->path . DIRECTORY_SEPARATOR . 'index.'. $fileType;
}
# add the modified date for the file
$meta['meta']['modified'] = file_exists($filePath) ? date("Y-m-d",filemtime($filePath)) : date("Y-m-d");
return $meta;
}
public function generateDescription($content, $parsedown, $item)
{
$description = isset($content[1]) ? $content[1] : '';
# create description or abstract from content
if($description !== '')
{
$firstLineArray = $parsedown->text($description);
$description = strip_tags($parsedown->markup($firstLineArray, $item->urlAbs));
# if description is very short
if(strlen($description) < 100 && isset($content[2]))
{
$secondLineArray = $parsedown->text($content[2]);
$description .= ' ' . strip_tags($parsedown->markup($secondLineArray, $item->urlAbs));
}
# if description is too long
if(strlen($description) > 300)
{
$description = substr($description, 0, 300);
$lastSpace = strrpos($description, ' ');
$description = substr($description, 0, $lastSpace);
}
}
return $description;
}
public function transformPagesToPosts($folder){
$filetypes = array('md', 'txt', 'yaml');
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
# set default date
$date = date('Y-m-d', time());
$time = date('H-i', time());
$meta = $this->getYaml('content', $page->pathWithoutType . '.yaml');
if($meta)
{
# get dates from meta
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
# set time
if(isset($meta['meta']['time']))
{
$time = $meta['meta']['time'];
}
}
$datetime = $date . '-' . $time;
$datetime = implode(explode('-', $datetime));
$datetime = substr($datetime,0,12);
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
}
}
public function transformPostsToPages($folder){
$filetypes = array('md', 'txt', 'yaml');
$index = 0;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
$order = $index;
if($index < 10)
{
$order = '0' . $index;
}
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
$index++;
}
}
}

View File

@@ -2,8 +2,6 @@
namespace Typemill\Models;
use Typemill\Extensions\ParsedownExtension;
class WriteYaml extends Write
{
/**
@@ -37,235 +35,4 @@ class WriteYaml extends Write
}
return false;
}
# used by contentApiController (backend) and pageController (frontend)
public function getPageMeta($settings, $item)
{
$meta = $this->getYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml');
if(!$meta)
{
return false;
}
# compare with meta that are in use right now (e.g. changed theme, disabled plugin)
$metascheme = $this->getYaml('cache', 'metatabs.yaml');
if($metascheme)
{
$meta = $this->whitelistMeta($meta,$metascheme);
}
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
return $meta;
}
# used by contentApiController (backend) and pageController (frontend)
public function getPageMetaDefaults($content, $settings, $item)
{
# initialize parsedown extension
$parsedown = new ParsedownExtension();
# if content is not an array, then transform it
if(!is_array($content))
{
# turn markdown into an array of markdown-blocks
$content = $parsedown->markdownToArrayBlocks($content);
}
$title = false;
# delete markdown from title
if(isset($content[0]))
{
$title = trim($content[0], "# ");
}
$description = false;
# delete markdown from title
if(isset($content[1]))
{
$firstLineArray = $parsedown->text($content[1]);
$description = strip_tags($parsedown->markup($firstLineArray, $item->urlAbs));
$description = substr($description, 0, 300);
$lastSpace = strrpos($description, ' ');
$description = substr($description, 0, $lastSpace);
}
$author = $settings['author'];
if(isset($_SESSION))
{
if(isset($_SESSION['firstname']) && $_SESSION['firstname'] !='' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
{
$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
}
elseif(isset($_SESSION['user']))
{
$author = $_SESSION['user'];
}
}
# create new meta-file
$meta = [
'meta' => [
'title' => $title,
'description' => $description,
'author' => $author,
'created' => date("Y-m-d"),
'time' => date("H-i-s"),
]
];
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
return $meta;
}
private function whitelistMeta($meta, $metascheme)
{
# we have only 2 dimensions, so no recursive needed
foreach($meta as $tab => $values)
{
if(!isset($metascheme[$tab]))
{
unset($meta[$tab]);
}
foreach($values as $key => $value)
{
if(!isset($metascheme[$tab][$key]))
{
unset($meta[$tab][$key]);
}
}
}
return $meta;
}
private function addFileTimeToMeta($meta, $item, $settings)
{
$filePath = $settings['contentFolder'] . $item->path;
$fileType = isset($item->fileType) ? $item->fileType : 'md';
# check if url is a folder.
if($item->elementType == 'folder')
{
$filePath = $settings['contentFolder'] . $item->path . DIRECTORY_SEPARATOR . 'index.'. $fileType;
}
# add the modified date for the file
$meta['meta']['modified'] = file_exists($filePath) ? date("Y-m-d",filemtime($filePath)) : false;
return $meta;
}
public function transformPagesToPosts($folder){
$filetypes = array('md', 'txt', 'yaml');
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
# set default date
$date = date('Y-m-d', time());
$time = date('H-i', time());
$meta = $this->getYaml('content', $page->pathWithoutType . '.yaml');
if($meta)
{
# get dates from meta
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
# set time
if(isset($meta['meta']['time']))
{
$time = $meta['meta']['time'];
}
}
$datetime = $date . '-' . $time;
$datetime = implode(explode('-', $datetime));
$datetime = substr($datetime,0,12);
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
}
}
public function transformPostsToPages($folder){
$filetypes = array('md', 'txt', 'yaml');
$index = 0;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
$order = $index;
if($index < 10)
{
$order = '0' . $index;
}
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
$index++;
}
}
}

View File

@@ -27,14 +27,14 @@ class Settings
$themes = array_diff(scandir($themefolder), array('..', '.'));
$firsttheme = reset($themes);
# if there is a theme with valid theme settings-file
if($firsttheme && self::getObjectSettings('themes', $firsttheme))
# if there is a theme with an index.twig-file
if($firsttheme && file_exists($themefolder . $firsttheme . DIRECTORY_SEPARATOR . 'index.twig'))
{
$settings['theme'] = $firsttheme;
}
else
{
die('There is no theme in the theme-folder. Please add a theme from https://themes.typemill.net');
die('You need at least one theme with an index.twig-file in your theme-folder.');
}
}
@@ -166,6 +166,7 @@ class Settings
'startpage' => true,
'author' => true,
'year' => true,
'headlineanchors' => true,
'theme' => true,
'editor' => true,
'formats' => true,

View File

@@ -141,18 +141,28 @@ a.tm-download::before{
width: 30px;
height: 30px;
line-height: 30px;
font-family: "Comic Sans MS",cursive,sans-serif;
font-family: "Comic Sans MS",cursive,sans-serif;
font-size: 1.3em;
font-weight: 900;
border: 2px solid #e0474c;
border-radius: 50%;
text-align: center;
text-decoration: underline;
text-decoration: none;
}
a.tm-download:hover::before{
text-decoration:underline;
text-decoration: none;
color: #fff;
background: #e0474c;
}
a.tm-heading-anchor {
display: none;
position: absolute;
top: 0;
left: -1em;
width: 1em;
opacity: 0;
}
/********************
* COMMONS *

View File

@@ -35,8 +35,15 @@ let determiner = {
}
return false;
},
video: function(block,lines,firstChar,secondChar,thirdChar){
if( (firstChar == '!' && secondChar == '[' && lines[0].indexOf('.youtube') != -1) || (firstChar == '[' && secondChar == '!' && lines[0].indexOf('.youtube') != -1) )
{
return "video-component";
}
return false;
},
image: function(block,lines,firstChar,secondChar,thirdChar){
if( (firstChar == '!' && secondChar == '[') || (firstChar == '[' && secondChar == '!' && thirdChar == '[') )
if( (firstChar == '!' && secondChar == '[' ) || (firstChar == '[' && secondChar == '!' && thirdChar == '[') )
{
return "image-component";
}

View File

@@ -253,6 +253,16 @@ const contentComponent = Vue.component('content-block', {
{
params.new = true;
}
else
{
var oldVideoID = this.$root.$data.blockMarkdown.match(/#.*? /);
if(this.compmarkdown.indexOf(oldVideoID[0].substring(1).trim()) !== -1)
{
this.activatePage();
this.switchToPreviewMode();
return;
}
}
}
else if(this.componentType == 'file-component')
{
@@ -282,7 +292,7 @@ const contentComponent = Vue.component('content-block', {
self.activatePage();
publishController.errors.message = "Looks like you are logged out. Please login and try again.";
}
else if(response)
else if(response)
{
self.activatePage();
@@ -342,9 +352,14 @@ const contentComponent = Vue.component('content-block', {
self.$root.checkMath(result.id);
/* check youtube here */
if(thisBlockType == "video-component" || thisBlockType == "image-component")
if(thisBlockType == "video-component")
{
self.$root.checkVideo(result.id);
setTimeout(function(){
self.$nextTick(function ()
{
self.$root.checkVideo(result.id);
});
}, 300);
}
/* update the navigation and mark navigation item as modified */
@@ -1192,7 +1207,6 @@ const definitionComponent = Vue.component('definition-component', {
},
})
const videoComponent = Vue.component('video-component', {
props: ['compmarkdown', 'disabled', 'load'],
template: '<div class="video dropbox">' +
@@ -1200,6 +1214,20 @@ const videoComponent = Vue.component('video-component', {
'<label for="video">{{ $t(\'Link to video\') }}: </label><input type="url" ref="markdown" placeholder="https://www.youtube.com/watch?v=" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown">' +
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
'</div>',
mounted: function(){
this.$refs.markdown.focus();
if(this.compmarkdown)
{
var videoid = this.compmarkdown.match(/#.*? /);
if(videoid)
{
var event = { 'target': { 'value': 'https://www.youtube.com/watch?v=' + videoid[0].trim().substring(1) }};
this.updatemarkdown(event);
}
}
},
methods: {
updatemarkdown: function(event)
{
@@ -1208,7 +1236,6 @@ const videoComponent = Vue.component('video-component', {
},
})
const imageComponent = Vue.component('image-component', {
props: ['compmarkdown', 'disabled'],
template: '<div class="dropbox">' +
@@ -1231,7 +1258,7 @@ const imageComponent = Vue.component('image-component', {
'<label for="imgtitle">{{ $t(\'Title\') }}: </label><input name="imgtitle" type="text" placeholder="title" v-model="imgtitle" @input="createmarkdown" max="64" />' +
'<label for="imgcaption">{{ $t(\'Caption\') }}: </label><input title="imgcaption" type="text" placeholder="caption" v-model="imgcaption" @input="createmarkdown" max="140" />' +
'<label for="imgurl">{{ $t(\'Link\') }}: </label><input title="imgurl" type="url" placeholder="url" v-model="imglink" @input="createmarkdown" />' +
'<label for="imgclass">{{ $t(\'Class\') }}: </label><select title="imgclass" v-model="imgclass" @change="createmarkdown"><option value="center">{{ $t(\'Center\') }}</option><option value="left">{{ $t(\'Left\') }}</option><option value="right">{{ $t(\'Right\') }}</option><option value="youtube">Youtube</option><option value="vimeo">Vimeo</option></select>' +
'<label for="imgclass">{{ $t(\'Class\') }}: </label><select title="imgclass" v-model="imgclass" @change="createmarkdown"><option value="center">{{ $t(\'Center\') }}</option><option value="left">{{ $t(\'Left\') }}</option><option value="right">{{ $t(\'Right\') }}</option></select>' +
'<input title="imgid" type="hidden" placeholder="id" v-model="imgid" @input="createmarkdown" max="140" />' +
'</div></div>',
data: function(){
@@ -2266,7 +2293,6 @@ let editor = new Vue({
}
if(response)
{
var result = JSON.parse(response);
if(result.errors)
@@ -2277,7 +2303,7 @@ let editor = new Vue({
{
self.markdown = result.data;
/* make math plugin working */
/* activate math plugin */
if (typeof renderMathInElement === "function") {
self.$nextTick(function () {
@@ -2419,29 +2445,26 @@ let editor = new Vue({
},
initiateVideo()
{
/* check for youtube videos */
/* check for youtube videos on first page load */
if (typeof typemillUtilities !== "undefined")
{
this.$nextTick(function () {
typemillUtilities.start();
typemillUtilities.start();
});
}
},
checkVideo(elementid)
{
/* check for youtube videos */
/* check for youtube videos for new blox */
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);
typemillUtilities.addYoutubePlayButton(imageElement[0]);
}
}
}

View File

@@ -178,6 +178,7 @@ const navcomponent = Vue.component('navigation', {
{
// evt.item.classList.remove("load");
self.$root.$data.items = result.data;
self.newItem = '';
self.showForm = false;
}
}
@@ -280,6 +281,7 @@ let navi = new Vue({
if(result.data)
{
self.items = result.data;
self.newItem = '';
self.showForm = false;
}
}
@@ -312,6 +314,7 @@ let navi = new Vue({
if(result.data)
{
self.items = result.data;
self.newItem = '';
self.homepage = result.homepage;
}
}

View File

@@ -69,6 +69,11 @@ let publishController = new Vue({
}
else
{
if(result.meta)
{
meta.formData = result.meta;
}
self.draftDisabled = "disabled";
self.publishResult = "success";
self.publishStatus = false;

View File

@@ -196,6 +196,7 @@
const myaxios = axios.create();
myaxios.defaults.baseURL = "{{ base_url }}";
</script>
<script src="{{ base_url }}/system/author/js/typemillutils.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/vue.min.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/vue-i18n.min.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/autosize.min.js?20200405"></script>
@@ -217,7 +218,6 @@
<script src="{{ base_url }}/system/author/js/vuedraggable.umd.min.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/vue-navi.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/vue-meta.js?20200405"></script>
<script src="{{ base_url }}/system/author/js/lazy-video.js?20200405"></script>
{{ assets.renderJS() }}

View File

@@ -7,7 +7,7 @@
{% if field.type == 'textarea' %}
<textarea name="{{ itemName }}[{{ field.name }}]"{{field.getAttributeValues() }}{{ field.getAttributes() }}>{{ field.getContent() }}</textarea>
<textarea id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]"{{field.getAttributeValues() }}{{ field.getAttributes() }}>{{ field.getContent() }}</textarea>
{% elseif field.type == 'paragraph' %}
@@ -16,7 +16,7 @@
{% elseif field.type == 'checkbox' %}
<label class="control-group">{{ __( field.getCheckboxLabel() ) }}
<input type="checkbox" name="{{ itemName}}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
<input type="checkbox" id="{{ itemName}}[{{ field.name }}]" name="{{ itemName}}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
<span class="checkmark"></span>
</label>
@@ -27,7 +27,7 @@
{% for value,label in options %}
<label class="control-group">{{ __( label ) }}
<input type="checkbox" name="{{ itemName }}[{{ field.name }}][{{value}}]" {{ settings[object][itemName][field.name][value] ? ' checked' : '' }}>
<input type="checkbox" id="{{ itemName }}[{{ field.name }}][{{value}}]" name="{{ itemName }}[{{ field.name }}][{{value}}]" {{ settings[object][itemName][field.name][value] ? ' checked' : '' }}>
<span class="checkmark"></span>
</label>
@@ -37,7 +37,7 @@
{% set options = field.getOptions() %}
<select name="{{ itemName }}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
<select id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
{% for value,label in options %}
<option value="{{ value }}" {{ (value == field.getAttributeValue('value')) ? ' selected' : '' }}>{{ label }}</option>
{% endfor %}
@@ -50,7 +50,7 @@
{% for value,label in options %}
<label class="control-group">{{ label }}
<input type="radio" name="{{ itemName }}[{{ field.name }}]" value="{{ value }}" {{ (value == settings[object][itemName][field.name]) ? ' checked' : '' }}>
<input type="radio" id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]" value="{{ value }}" {{ (value == settings[object][itemName][field.name]) ? ' checked' : '' }}>
<span class="radiomark"></span>
</label>
@@ -58,7 +58,7 @@
{% else %}
<input name="{{itemName}}[{{ field.name }}]" type="{{ field.type }}"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
<input id="{{itemName}}[{{ field.name }}]" name="{{itemName}}[{{ field.name }}]" type="{{ field.type }}"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
{% endif %}

View File

@@ -63,7 +63,12 @@
</div><div class="medium">
<label for="settings[sitemap]">Google Sitemap <small>({{ __('Readonly') }})</small></label>
<input type="text" name="settings[sitemap]" id="sitemap" readonly value="{{ base_url }}/cache/sitemap.xml" />
</div><div class="medium{{ errors.settings.logo ? ' error' : '' }}">
</div>
<hr>
<header class="headline">
<h2>{{ __('General Presentation') }}</h2>
</header>
<div class="medium{{ errors.settings.logo ? ' error' : '' }}">
<label for="settings[logo]">Logo <small>(jpg,jpeg,png,svg)</small></label>
<div class="flex fileinput">
<button class="deletefilebutton w-10 bg-tm-gray bn hover-bg-tm-red hover-white">x</button>
@@ -91,6 +96,12 @@
{% if errors.settings.favicon %}
<span class="error">{{ errors.settings.favicon | first }}</span>
{% endif %}
</div><div class="medium{{ errors.settings.headlineanchors ? ' error' : '' }}">
<label for="settings[headlineanchors]">{{ __('Headline Anchors') }} *</label>
<label class="control-group">{{ __('Show anchors next to headlines') }}
<input name="settings[headlineanchors]" type="checkbox" {% if (old.settings.headlineanchors) %} checked {% endif %}>
<span class="checkmark"></span>
</label>
</div>
<hr>
<header class="headline">
@@ -130,4 +141,4 @@
</form>
</div>
{% endblock %}
{% endblock %}

View File

@@ -16,14 +16,36 @@
{{ content }}
<div class="toc-nav">
<ul>
{% if item.contains == 'pages' %}
<div class="toc-nav">
<ul>
{% for element in item.folderContent %}
<li class="level-2"><a href="{{ element.urlAbs }}">{% if settings.themes.typemill.chapnum %}{{ element.chapter }} {% endif %}{{ element.name }}</a></li>
{% endfor %}
</ul>
</div>
{% elseif item.contains == 'posts' %}
<ul class="post">
{% for element in item.folderContent %}
<li class="level-2"><a href="{{ element.urlAbs }}">{% if settings.themes.typemill.chapnum %}{{ element.chapter }} {% endif %}{{ element.name }}</a></li>
{% set post = getPageMeta(settings, element) %}
{% set date = element.order[0:4] ~ '-' ~ element.order[4:2] ~ '-' ~ element.order[6:2] %}
<li class="post-entry">
<a href="{{ element.urlAbs }}"><h2>{{ post.meta.title }}</h2></a>
<small><time datetime="{{date}}">{{ date | date("d.m.Y") }}</time> | {{ post.meta.author }}</small>
<p>{{ post.meta.description }}</p>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

View File

@@ -1,5 +1,7 @@
{% extends '/partials/layoutCover.twig' %}
{% block title %}{{ metatabs.meta.title | default(title) }} | {{ settings.title }}{% endblock %}
{% block content %}
{% if logo and settings.themes.typemill.coverlogo %}

View File

@@ -67,7 +67,7 @@ pre,code{
* HEADLINES *
********************/
h1, h2, h3, h4, h5, h6{ font-weight: 700; line-height: 1em; }
h1, h2, h3, h4, h5, h6{ font-weight: 700; line-height: 1em; position: relative;}
h1{ font-size: 2.2em; margin: 1.4em 0 0.6em; }
h2{ font-size: 1.6em; margin: 1.3em 0 0.6em; }
h3{ font-size: 1.3em; margin: 1.2em 0 0.6em; }
@@ -350,11 +350,11 @@ article img.youtube{
position: relative;
max-width: 560px;
}
article .video-container{
.video-container{
position: relative;
text-align: center;
}
article button.play-video {
button.play-video {
position: absolute;
top: 50%;
left: 50%;
@@ -369,11 +369,11 @@ article button.play-video {
padding: 0;
text-align: center;
}
article button.play-video:hover {
button.play-video:hover {
background: #cc4146;
cursor: pointer;
}
article button.play-video::after {
button.play-video::after {
position: absolute;
top: 50%;
margin: -20px 0 0 -15px;
@@ -548,6 +548,11 @@ ul,ol{
padding-left: 0px;
margin-left: 18px;
}
ul.post{
list-style: none;
padding: 0 0 0 0;
margin: 0 0 0 0;
}
blockquote{
border-left: 4px solid #e0474c;
background: #f9f8f6;
@@ -600,17 +605,43 @@ a.tm-download::before{
width: 30px;
height: 30px;
line-height: 30px;
font-family: "Comic Sans MS",cursive,sans-serif;
font-family: "Comic Sans MS",cursive,sans-serif;
font-size: 1.3em;
font-weight: 900;
border: 2px solid #e0474c;
border-radius: 50%;
text-align: center;
text-decoration: underline;
text-decoration: none;
}
a.tm-download:hover::before{
text-decoration:underline;
text-decoration: none;
color: #fff;
background: #e0474c;
}
a.tm-heading-anchor {
display: none;
position: absolute;
top: 0;
left: -1em;
width: 1em;
opacity: 0;
}
a.tm-heading-anchor:hover,a.tm-heading-anchor:focus {
opacity: 1;
text-decoration: none;
}
h2:focus > .tm-heading-anchor,
h2:hover > .tm-heading-anchor,
h3:focus > .tm-heading-anchor,
h3:hover > .tm-heading-anchor,
h4:focus > .tm-heading-anchor,
h4:hover > .tm-heading-anchor,
h5:focus > .tm-heading-anchor,
h5:hover > .tm-heading-anchor,
h6:focus > .tm-heading-anchor,
h6:hover > .tm-heading-anchor{
opacity: .75;
}
/************************
@@ -735,6 +766,9 @@ img.myClass{
.cover h1{
font-size: 4em;
}
a.tm-heading-anchor{
display: block;
}
.github{
position:absolute;
display:block;

View File

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

View File

@@ -51,6 +51,9 @@
</div>
{% block javascripts %}
<script src="{{ base_url }}/themes/typemill/js/script.js"></script>
<script src="{{ base_url }}/system/author/js/typemillutils.js?20200418"></script>
<script>typemillUtilities.start();</script>
{{ assets.renderJS() }}

View File

@@ -15,7 +15,7 @@
{% endif %}
{% if (element.elementType == 'folder') %}
<a href="{{ element.urlAbs }}">{% if chapnum %}{{ element.chapter }}. {% endif %}{{ element.name }}</a>
{% if (element.folderContent|length > 0) %}
{% if ( element.folderContent|length > 0 ) and (element.contains == 'pages') %}
<ul>
{{ macros.loop_over(element.folderContent,chapnum) }}
</ul>

View File

@@ -1,5 +1,5 @@
name: Typemill Theme
version: 1.2.3
version: 1.2.4
description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. No JavaScript is used.
author: Sebastian Schürmanns
homepage: https://typemill.net