Version 1.3.5 Consolidation
This commit is contained in:
parent
5a8968efde
commit
55704bd69f
34 changed files with 726 additions and 426 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ class AuthController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* show login form
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
348
system/Models/WriteMeta.php
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() }}
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() }}
|
||||
|
|
|
@ -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() }}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue