diff --git a/content/00-welcome/01-write-content.md b/content/00-welcome/01-write-content.md index 0f241440a714a3809e88f92af1b82ba60ad5193e..140d0d7c134a14d64270b2b0545003a4d1e55c7c 100644 --- a/content/00-welcome/01-write-content.md +++ b/content/00-welcome/01-write-content.md @@ -6,7 +6,7 @@ Typemill is a simple Flat File Content Management System (CMS). We (the communit You can create, structure and reorder all pages with the navigation on the left. To structure your content, you can create new folders and files with the "add item" button. To reorder the pages, just drag an item and drop it wherever you want. Play around with it and you will notice, that it works pretty similar to the folder- and file-system of your laptop. And in fact, this is exactly what Typemill does in the background: It stores your content in files and folders on the server. -However, there are some limitations when you try to reorder elements. For example, you cannot move a complete folder to another folder. Click on the question-mark at the top of the navigation for detailed information. +However, there are some limitations when you try to reorder elements. For example, you cannot move a complete folder to another folder, because this would change all the urls of the pages inside that folder, which is a nightmare for readers and search engines. ## The Editor diff --git a/system/Controllers/ArticleApiController.php b/system/Controllers/ArticleApiController.php index 5ed5e19ddef76c8758957898e08c25591a0f2910..034260a13baa282ff3f69d1f5879c073d01c90c5 100644 --- a/system/Controllers/ArticleApiController.php +++ b/system/Controllers/ArticleApiController.php @@ -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); + # complete the page meta if title or description not set + $writeMeta = new WriteMeta(); + $meta = $writeMeta->completePageMeta($this->content, $this->settings, $this->item); + # dispatch event - $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($this->item)); + $page = ['content' => $this->content, 'meta' => $meta, 'item' => $this->item]; + $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page)); - return $response->withJson(['success'], 200); + 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); diff --git a/system/Controllers/AuthController.php b/system/Controllers/AuthController.php index b5fa7002e522f52ff1876c166967fbd6494c3d90..b8c89219f4fe4abd9413afb8f684a867100f74b8 100644 --- a/system/Controllers/AuthController.php +++ b/system/Controllers/AuthController.php @@ -24,7 +24,6 @@ class AuthController extends Controller } } - /** * show login form * diff --git a/system/Controllers/BlockApiController.php b/system/Controllers/BlockApiController.php index 9c77ff43dc94c3884278f01f96db46420dbe1714..3e5a0afb0370a6b143ffd6bb4254cd61c4fae7a6 100644 --- a/system/Controllers/BlockApiController.php +++ b/system/Controllers/BlockApiController.php @@ -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'] = '{#' . $videoID. ' .' . $class . '}'; $request = $request->withParsedBody($this->params); diff --git a/system/Controllers/ContentController.php b/system/Controllers/ContentController.php index 78ea990d77de075cb7bb95cb1e3d2a3fe26fae6e..87dc47e3041a4bf59bb0e80da33787b749b9db20 100644 --- a/system/Controllers/ContentController.php +++ b/system/Controllers/ContentController.php @@ -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.']; } } diff --git a/system/Controllers/Controller.php b/system/Controllers/Controller.php index 57e54cfb9ab777914d43901c987dbde48fc2b609..6dcad455d6231875995517938a2b8a915b6a7687 100644 --- a/system/Controllers/Controller.php +++ b/system/Controllers/Controller.php @@ -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); } diff --git a/system/Controllers/MediaApiController.php b/system/Controllers/MediaApiController.php index a7211977dc20edf2af37e38837e42e6cd465ce5b..5b0ec9b9919129ca79109fb96dd1dacb3664937d 100644 --- a/system/Controllers/MediaApiController.php +++ b/system/Controllers/MediaApiController.php @@ -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', ); } } \ No newline at end of file diff --git a/system/Controllers/MetaApiController.php b/system/Controllers/MetaApiController.php index ab997a1f1c95520d2be3a90538e210d273430d91..30c6df949694720a4c31e2fdd4d7937bee33bd83 100644 --- a/system/Controllers/MetaApiController.php +++ b/system/Controllers/MetaApiController.php @@ -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. \ No newline at end of file +} \ No newline at end of file diff --git a/system/Controllers/PageController.php b/system/Controllers/PageController.php index b721941c310091756713cae15b76dd1d2ca3fcef..a05c9e212445ccefdfdba2f234e613c88d929a8a 100644 --- a/system/Controllers/PageController.php +++ b/system/Controllers/PageController.php @@ -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("", $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) diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index bad290fcf29374f1a541e64650006557ef594410..23af2e496eb00ce9ddb791393fe7ac2b7c8e0b4f 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -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(); diff --git a/system/Controllers/SetupController.php b/system/Controllers/SetupController.php index e149ed1f4797a7f442b0729b7691d7115d4fa27f..35e10a38b4ef187c7b1ac5b27fc7c0c59655048e 100644 --- a/system/Controllers/SetupController.php +++ b/system/Controllers/SetupController.php @@ -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) diff --git a/system/Extensions/ParsedownExtension.php b/system/Extensions/ParsedownExtension.php index 34771a25b0bc63bac291e1cfe4c4d0e0908d5b7d..77d305aa09b36077d1c7cc98c26244f564cee858 100644 --- a/system/Extensions/ParsedownExtension.php +++ b/system/Extensions/ParsedownExtension.php @@ -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; diff --git a/system/Extensions/TwigMetaExtension.php b/system/Extensions/TwigMetaExtension.php index 82da0606bddd975ee3d289477bf2ad61a4e7bc9c..0d33399b4462bb3c8a45ce833858e712ab05c847 100644 --- a/system/Extensions/TwigMetaExtension.php +++ b/system/Extensions/TwigMetaExtension.php @@ -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; } diff --git a/system/Models/ProcessAssets.php b/system/Models/ProcessAssets.php index 98bed4d8d9c573db00d815c0151ab6899b9371b7..74943886f1b547f0077b29b68299ecee341ebeeb 100644 --- a/system/Models/ProcessAssets.php +++ b/system/Models/ProcessAssets.php @@ -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; diff --git a/system/Models/ProcessImage.php b/system/Models/ProcessImage.php index f3c4b0a2cdd80403c4c8c8abab348a0d2a8d1862..e6a1d18f54ee080ec00260b923d227e7ec03a4e7 100644 --- a/system/Models/ProcessImage.php +++ b/system/Models/ProcessImage.php @@ -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; } - } \ No newline at end of file diff --git a/system/Models/WriteMeta.php b/system/Models/WriteMeta.php new file mode 100644 index 0000000000000000000000000000000000000000..f04d7af0ba8d26d8c52a910005b3704974a0139c --- /dev/null +++ b/system/Models/WriteMeta.php @@ -0,0 +1,348 @@ +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++; + } + } +} \ No newline at end of file diff --git a/system/Models/WriteYaml.php b/system/Models/WriteYaml.php index 283ee2d223cde33d68ec6552cedb83cd27b861a8..6473dcc8ada3bb19f854d5efbd6381aa272a1778 100644 --- a/system/Models/WriteYaml.php +++ b/system/Models/WriteYaml.php @@ -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++; - } - } } \ No newline at end of file diff --git a/system/Settings.php b/system/Settings.php index 7dde0a967b7085d0409dd4bd56b42334e852aa60..9bf141c41cb14c21ff8eebabc7c8c0647d89e02f 100644 --- a/system/Settings.php +++ b/system/Settings.php @@ -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, diff --git a/system/author/css/style.css b/system/author/css/style.css index 619798771d252e3837697a58f8848a1a35d90e84..b4f6c00a1b852fed4018477ebb0602916ff4556e 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -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 * diff --git a/system/author/js/lazy-video.js b/system/author/js/typemillutils.js similarity index 100% rename from system/author/js/lazy-video.js rename to system/author/js/typemillutils.js diff --git a/system/author/js/vue-blox-config.js b/system/author/js/vue-blox-config.js index 170b5010e7ff169829f74fe5eae4be16fb9b0108..8368a4ab73599c3466eee03b5d3af38b6dbc729f 100644 --- a/system/author/js/vue-blox-config.js +++ b/system/author/js/vue-blox-config.js @@ -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"; } diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index a64347bbfc432069ddc7e735df55778605a4b96a..e2006e14d280db194ae037f5bbc0ff643c1a20a4 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -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: '