diff --git a/.htaccess b/.htaccess index 39493b6..ef390bb 100644 --- a/.htaccess +++ b/.htaccess @@ -64,7 +64,7 @@ RewriteRule ^(system\/author\/img\/) - [L] RewriteRule ^(system\/author\/js\/) - [L] # redirect all other direct requests to the following physical folders to the index.php so pages with same name work -RewriteRule ^(system|content|data|settings) index.php [QSA,L] +RewriteRule ^(system|content|data|settings|(media\/files\/)) index.php [QSA,L] # disallow browsing other folders generally Options -Indexes diff --git a/content/01-cyanine-theme/03-content-elements.md b/content/01-cyanine-theme/03-content-elements.md index a3e7c38..88d339d 100644 --- a/content/01-cyanine-theme/03-content-elements.md +++ b/content/01-cyanine-theme/03-content-elements.md @@ -2,10 +2,7 @@ Cyanine provides a lot of other settings for your content area. For example: -* Add an edit-button for github, gitlab or other plattforms. -* Show the author. -* Show the publish date. -* Show the chapter numbers in the navigation. +[ebook (EPUB, 496.65 KB)](media/files/ebook.epub){.tm-download file-epub} The Cyanine theme supports all content elements like tables, images, notices or downloads. It also supports anchor-links next to headlines, so you can deep link to certain content sections of your page. You can activate the anchors in the system settings of Typemill. diff --git a/system/Controllers/ControllerAuthorArticleApi.php b/system/Controllers/ControllerAuthorArticleApi.php index d021346..5f061c3 100644 --- a/system/Controllers/ControllerAuthorArticleApi.php +++ b/system/Controllers/ControllerAuthorArticleApi.php @@ -180,7 +180,7 @@ class ControllerAuthorArticleApi extends ControllerAuthor # check if it is a folder and if the folder has published pages. $message = false; - if($this->item->elementType == 'folder') + if($this->item->elementType == 'folder' && isset($this->item->folderContent)) { foreach($this->item->folderContent as $folderContent) { diff --git a/system/Controllers/ControllerAuthorMediaApi.php b/system/Controllers/ControllerAuthorMediaApi.php index 1be1629..4840ce9 100644 --- a/system/Controllers/ControllerAuthorMediaApi.php +++ b/system/Controllers/ControllerAuthorMediaApi.php @@ -6,6 +6,7 @@ use Slim\Http\Request; use Slim\Http\Response; use Typemill\Models\ProcessImage; use Typemill\Models\ProcessFile; +use Typemill\Models\WriteYaml; use Typemill\Controllers\ControllerAuthorBlockApi; class ControllerAuthorMediaApi extends ControllerAuthor @@ -92,6 +93,69 @@ class ControllerAuthorMediaApi extends ControllerAuthor return $response->withJson(['errors' => 'file not found or file name invalid'],404); } + public function getFileRestrictions(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $restriction = 'all'; + + $userroles = $this->c->acl->getRoles(); + + if(isset($this->params['filename']) && $this->params['filename'] != '') + { + $writeYaml = new WriteYaml(); + $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); + if(isset($restrictions[$this->params['filename']])) + { + $restriction = $restrictions[$this->params['filename']]; + } + } + + return $response->withJson(['userroles' => $userroles, 'restriction' => $restriction]); + } + + public function updateFileRestrictions(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $filename = isset($this->params['filename']) ? $this->params['filename'] : false; + $role = isset($this->params['role']) ? $this->params['role'] : false; + + if(!$filename OR !$role) + { + return $response->withJson(['errors' => ['message' => 'Filename or userrole is missing.']], 422); + } + + $userroles = $this->c->acl->getRoles(); + + if($role != 'all' AND !in_array($role, $userroles)) + { + return $response->withJson(['errors' => ['message' => 'Userrole is unknown.']], 422); + } + + $writeYaml = new WriteYaml(); + $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); + if(!$restrictions) + { + $restrictions = []; + } + + if($role == 'all') + { + unset($restrictions[$filename]); + } + else + { + $restrictions[$filename] = $role; + } + + $writeYaml->updateYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml', $restrictions); + + return $response->withJson(['restrictions' => $restrictions]); + } + public function createImage(Request $request, Response $response, $args) { # get params from call @@ -382,14 +446,14 @@ class ControllerAuthorMediaApi extends ControllerAuthor return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); } - $tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes); + $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); if(!$tmpImage) { return $response->withJson(array('errors' => 'could not create temporary image')); } - $imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID); + $imageUrl = $imageProcessor->publishImage(); if($imageUrl) { $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; diff --git a/system/Controllers/ControllerAuthorMetaApi.php b/system/Controllers/ControllerAuthorMetaApi.php index 7634014..ab36e3f 100644 --- a/system/Controllers/ControllerAuthorMetaApi.php +++ b/system/Controllers/ControllerAuthorMetaApi.php @@ -313,6 +313,7 @@ class ControllerAuthorMetaApi extends ControllerAuthor # normalize the meta-input $metaInput['navtitle'] = (isset($metaInput['navtitle']) && $metaInput['navtitle'] !== null )? $metaInput['navtitle'] : ''; $metaInput['hide'] = (isset($metaInput['hide']) && $metaInput['hide'] !== null) ? $metaInput['hide'] : false; + $metaInput['noindex'] = (isset($metaInput['noindex']) && $metaInput['noindex'] !== null) ? $metaInput['noindex'] : false; # input values are empty but entry in structure exists if(!$metaInput['hide'] && $metaInput['navtitle'] == "" && isset($extended[$this->item->urlRelWoF])) @@ -327,10 +328,12 @@ class ControllerAuthorMetaApi extends ControllerAuthor ($this->hasChanged($metaInput, $metaPage['meta'], 'navtitle')) OR ($this->hasChanged($metaInput, $metaPage['meta'], 'hide')) + OR + ($this->hasChanged($metaInput, $metaPage['meta'], 'noindex')) ) { # add new file data. Also makes sure that the value is set. - $extended[$this->item->urlRelWoF] = ['hide' => $metaInput['hide'], 'navtitle' => $metaInput['navtitle']]; + $extended[$this->item->urlRelWoF] = ['hide' => $metaInput['hide'], 'navtitle' => $metaInput['navtitle'], 'noindex' => $metaInput['noindex']]; $structure = true; } diff --git a/system/Controllers/ControllerDownload.php b/system/Controllers/ControllerDownload.php new file mode 100644 index 0000000..6d151f3 --- /dev/null +++ b/system/Controllers/ControllerDownload.php @@ -0,0 +1,144 @@ +c->get('settings')['rootPath']; + $mediapath = 'media/files/'; + $filepath = $root . $mediapath; + + if(!$filename) + { + die('the requested file does not exist.'); + } + + # validate + $allowedFiletypes = []; + if(!$this->validate($filepath, $filename, $allowedFiletypes)) + { + die('the requested file is not allowed.'); + } + + $writeYaml = new WriteYaml(); + $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); + + if($restrictions && isset($restrictions[$mediapath . $filename])) + { + $allowedrole = $restrictions[$mediapath . $filename]; + + if(!isset($_SESSION['role'])) + { + die("You have to be an authenticated $allowedrole to download this file."); + } + elseif( + $_SESSION['role'] != 'administrator' + AND $_SESSION['role'] != $allowedrole + AND !$this->c->acl->inheritsRole($_SESSION['role'], $allowedrole) + ) + { + die("You have to be a $allowedrole to download this file."); + } + } + + $file = $filepath . $filename; + + # for now we only allow one download + $this->sendDownload($file); + exit; + } + + /** + * Validate if the file exists and if + * there is a permission (download dir) to download this file + * + * You should ALWAYS call this method if you don't want + * somebody to download files not intended to be for the public. + * + * @param string $file GET parameter + * @param array $allowedFiletypes (defined in the head of this file) + * @return bool true if validation was successfull + */ + private function validate($path, $file, $allowedFiletypes) + { + $filepath = $path . $file; + + # check if file exists + if (!isset($filepath) || empty($filepath) || !file_exists($filepath)) + { + return false; + } + + # check allowed filetypes + if(!empty($allowedFiletypes)) + { + $fileAllowed = false; + foreach ($allowedFiletypes as $filetype) + { + if (strpos($file, $filetype) === (strlen($file) - strlen($filetype))) + { + $fileAllowed = true; //ends with $filetype + } + } + + if (!$fileAllowed) return false; + } + + # check download directory + if (strpos($file, '..') !== false) + { + return false; + } + return true; + } + + /** + * Download function. + * Sets the HTTP header and supplies the given file + * as a download to the browser. + * + * @param string $file path to file + */ + private function sendDownload($file) + { + # Parse information + $pathinfo = pathinfo($file); + $extension = strtolower($pathinfo['extension']); + $mimetype = null; + + # Get mimetype for extension + # This list can be extended as you need it. + # A good start to find mimetypes is the apache mime.types list + # http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + switch ($extension) { + case 'zip': $mimetype = "application/zip"; break; + default: $mimetype = "application/force-download"; + } + + # Required for some browsers like Safari and IE + if (ini_get('zlib.output_compression')) + { + ini_set('zlib.output_compression', 'Off'); + } + + header('Pragma: public'); + header('Content-Encoding: none'); + header('Expires: 0'); + header('Accept-Ranges: bytes'); # Allow support for download resume + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); # required for some browsers + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="'.basename($file).'";'); # Make the browser display the Save As dialog + header('Content-Transfer-Encoding: binary'); + header('Content-Length: '.filesize($file)); + ob_end_flush(); + readfile($file); # This is necessary in order to get it to actually download the file, otherwise it will be 0Kb + } +} \ No newline at end of file diff --git a/system/Controllers/ControllerShared.php b/system/Controllers/ControllerShared.php index 65df936..5b5ca46 100644 --- a/system/Controllers/ControllerShared.php +++ b/system/Controllers/ControllerShared.php @@ -177,7 +177,7 @@ abstract class ControllerShared $pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false ); # if there is content, then get the content details - if(count($pagetreeLive) > 0) + if($pagetreeLive && count($pagetreeLive) > 0) { # get the extended structure files with changes like navigation title or hidden pages $yaml = new writeYaml(); @@ -295,28 +295,37 @@ abstract class ControllerShared $opts = array( 'http'=>array( 'method'=>"GET", + 'ignore_errors' => true, 'timeout' => 5 ) ); - $context = stream_context_create($opts); + $context = stream_context_create($opts); - $resultBing = file_get_contents($pingBingUrl, false, $context); - $resultGoogle = file_get_contents($pingGoogleUrl, false, $context); + $responseBing = file_get_contents($pingBingUrl, false, $context); + $responseGoogle = file_get_contents($pingGoogleUrl, false, $context); } } public function generateUrlSets($structureLive) - { + { $urlset = ''; - + foreach($structureLive as $item) { - if($item->elementType == 'folder') + if($item->elementType == 'folder' && isset($item->noindex) && $item->noindex === true) + { + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif($item->elementType == 'folder') { $urlset = $this->addUrlSet($urlset, $item->urlAbs); - $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif(isset($item->noindex) && $item->noindex === true ) + { + continue; } else { diff --git a/system/Models/Folder.php b/system/Models/Folder.php index 201668c..1d2261f 100644 --- a/system/Models/Folder.php +++ b/system/Models/Folder.php @@ -154,8 +154,9 @@ class Folder # check if there are extended information if($extended && isset($extended[$item->urlRelWoF])) { - $item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name; - $item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false; + $item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name; + $item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false; + $item->noindex = (isset($extended[$item->urlRelWoF]['noindex']) && $extended[$item->urlRelWoF]['noindex'] === true) ? true : false; } # sort posts in descending order @@ -217,8 +218,9 @@ class Folder # check if there are extended information if($extended && isset($extended[$item->urlRelWoF])) { - $item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name; - $item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false; + $item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name; + $item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false; + $item->noindex = (isset($extended[$item->urlRelWoF]['noindex']) && $extended[$item->urlRelWoF]['noindex'] === true) ? true : false; } } diff --git a/system/Routes/Api.php b/system/Routes/Api.php index 09fec81..7630251 100644 --- a/system/Routes/Api.php +++ b/system/Routes/Api.php @@ -36,7 +36,7 @@ $app->post('/api/v1/block', ControllerAuthorBlockApi::class . ':addBlock')->setN $app->put('/api/v1/block', ControllerAuthorBlockApi::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router'])); $app->delete('/api/v1/block', ControllerAuthorBlockApi::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router'])); $app->put('/api/v1/moveblock', ControllerAuthorBlockApi::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router'])); -$app->post('/api/v1/video', ControllerAuthorBlockApi::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router'])); +$app->post('/api/v1/video', ControllerAuthorMediaApi::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router'])); $app->get('/api/v1/medialib/images', ControllerAuthorMediaApi::class . ':getMediaLibImages')->setName('api.medialibimg.get')->add(new RestrictApiAccess($container['router'])); $app->get('/api/v1/medialib/files', ControllerAuthorMediaApi::class . ':getMediaLibFiles')->setName('api.medialibfiles.get')->add(new RestrictApiAccess($container['router'])); @@ -44,6 +44,8 @@ $app->get('/api/v1/image', ControllerAuthorMediaApi::class . ':getImage')->setNa $app->post('/api/v1/image', ControllerAuthorMediaApi::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router'])); $app->put('/api/v1/image', ControllerAuthorMediaApi::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router'])); $app->delete('/api/v1/image', ControllerAuthorMediaApi::class . ':deleteImage')->setName('api.image.delete')->add(new RestrictApiAccess($container['router'])); +$app->get('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':getFileRestrictions')->setName('api.file.getrestrictions')->add(new RestrictApiAccess($container['router'])); +$app->post('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':updateFileRestrictions')->setName('api.file.updaterestrictions')->add(new RestrictApiAccess($container['router'])); $app->get('/api/v1/file', ControllerAuthorMediaApi::class . ':getFile')->setName('api.file.get')->add(new RestrictApiAccess($container['router'])); $app->post('/api/v1/file', ControllerAuthorMediaApi::class . ':uploadFile')->setName('api.file.upload')->add(new RestrictApiAccess($container['router'])); $app->put('/api/v1/file', ControllerAuthorMediaApi::class . ':publishFile')->setName('api.file.publish')->add(new RestrictApiAccess($container['router'])); diff --git a/system/Routes/Web.php b/system/Routes/Web.php index 6d34221..2a95a2e 100644 --- a/system/Routes/Web.php +++ b/system/Routes/Web.php @@ -1,6 +1,7 @@ get('/tm/content/raw[/{params:.*}]', ControllerAuthorEditor::class . ':sho $app->get('/tm/content/visual[/{params:.*}]', ControllerAuthorEditor::class . ':showBlox')->setName('content.visual')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view')); $app->get('/tm/content[/{params:.*}]', ControllerAuthorEditor::class . ':showEmpty')->setName('content.empty')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view')); +$app->get('/media/files[/{params:.*}]', ControllerDownload::class . ':download')->setName('download.file'); + foreach($routes as $pluginRoute) { $method = $pluginRoute['httpMethod']; diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index 9620996..96570cb 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -17,7 +17,7 @@ const contentComponent = Vue.component('content-block', { '
Choose a content-type
' + '
' + '
' + - '' + + '' + '' + '
' + '
' + @@ -1769,7 +1769,12 @@ const fileComponent = Vue.component('file-component', { '' + '' + '' + - '
', + '' + + '' + + '', data: function(){ return { maxsize: 20, // megabyte @@ -1779,7 +1784,9 @@ const fileComponent = Vue.component('file-component', { filetitle: '', fileextension: '', fileurl: '', - fileid: '' + fileid: '', + userroles: ['all'], + selectedrole: '', } }, mounted: function(){ @@ -1814,6 +1821,27 @@ const fileComponent = Vue.component('file-component', { this.fileurl = fileurl[0].slice(1,-1); } } + + self = this; + + myaxios.get('/api/v1/filerestrictions',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': this.fileurl, + } + }) + .then(function (response) { + self.userroles = self.userroles.concat(response.data.userroles); + self.selectedrole = response.data.restriction; + }) + .catch(function (error) + { + if(error.response) + { + } + }); }, methods: { openmedialib: function() @@ -1831,6 +1859,27 @@ const fileComponent = Vue.component('file-component', { { this.$emit('updatedMarkdown', event.target.value); }, + updaterestriction: function() + { + myaxios.post('/api/v1/filerestrictions',{ + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': this.fileurl, + 'role': this.selectedrole, + }) + .then(function (response) { + + }) + .catch(function (error) + { + if(error.response) + { + } + }); + + console.info(this.selectedrole); + }, createmarkdown: function() { var errors = false; @@ -1939,9 +1988,8 @@ for(var i = 0; i < formatConfig.length; i++) } let editor = new Vue({ - delimiters: ['${', '}'], + delimiters: ['${', '}'], el: '#blox', -/* components: componentList, */ data: { errors: [], root: document.getElementById("main").dataset.url, @@ -1960,7 +2008,7 @@ let editor = new Vue({ bloxOverlay: false, sortdisabled: false, showEditor: 'show', - formats: activeFormats + formats: activeFormats, }, mounted: function(){ @@ -1969,110 +2017,109 @@ let editor = new Vue({ var self = this; myaxios.post('/api/v1/article/html',{ - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - }) - .then(function (response) { - - var contenthtml = response.data.data; - self.title = contenthtml[0]; - self.html = contenthtml; - var initialcontent = document.getElementById("initial-content"); + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response){ + var contenthtml = response.data.data; + self.title = contenthtml[0]; + self.html = contenthtml; + var initialcontent = document.getElementById("initial-content"); - initialcontent.parentNode.removeChild(initialcontent); - + initialcontent.parentNode.removeChild(initialcontent); }) .catch(function (error) { - if(error.response) - { - self.errors.title = error.response.errors; + if(error.response) + { + self.errors.title = error.response.errors; } }); myaxios.post('/api/v1/article/markdown',{ - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - }) + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) .then(function (response) { - - self.markdown = response.data.data; - - /* activate math plugin */ - if (typeof renderMathInElement === "function") { - self.$nextTick(function () { - renderMathInElement(document.getElementById("blox")); - }); - } - - /* check for youtube videos */ - if (typeof typemillUtilities !== "undefined") { - setTimeout(function(){ + self.markdown = response.data.data; + + /* activate math plugin */ + if (typeof renderMathInElement === "function") { self.$nextTick(function () { - typemillUtilities.start(); + renderMathInElement(document.getElementById("blox")); }); - }, 200); - } + } + + /* check for youtube videos */ + if (typeof typemillUtilities !== "undefined") { + setTimeout(function(){ + self.$nextTick(function () { + typemillUtilities.start(); + }); + }, 200); + } }) .catch(function (error) { - if(error.response) - { - self.errors.title = error.response.errors; + if(error.response) + { + self.errors.title = error.response.errors; } }); - }, - methods: { - onStart: function() - { - }, - moveBlock: function(evt) - { - publishController.errors.message = false; + methods: { + onStart: function() + { - var self = this; + }, + moveBlock: function(evt) + { + publishController.errors.message = false; - myaxios.put('/api/v1/moveblock',{ - 'url': document.getElementById("path").value, - 'old_index': evt.oldIndex, - 'new_index': evt.newIndex, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - }) - .then(function (response) { + var self = this; + + myaxios.put('/api/v1/moveblock',{ + 'url': document.getElementById("path").value, + 'old_index': evt.oldIndex, + 'new_index': evt.newIndex, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) { - self.freeze = false; + self.freeze = false; - self.markdown = response.data.markdown; - self.blockMarkdown = ''; - self.blockType = ''; + self.markdown = response.data.markdown; + self.blockMarkdown = ''; + self.blockType = ''; - if(response.data.toc) - { - self.html.splice(response.data.toc.id, 1, response.data.toc); - } + if(response.data.toc) + { + self.html.splice(response.data.toc.id, 1, response.data.toc); + } - publishController.publishDisabled = false; - publishController.publishResult = ""; + publishController.publishDisabled = false; + publishController.publishResult = ""; - /* update the navigation and mark navigation item as modified */ - navi.getNavi(); + /* update the navigation and mark navigation item as modified */ + navi.getNavi(); - /* update the math if plugin is there */ - self.checkMath(params.new_index+1); - }) - .catch(function (error) + /* update the math if plugin is there */ + self.checkMath(params.new_index+1); + }) + .catch(function (error) + { + if(error) + { + publishController.publishDisabled = false; + } + if(error.response) { - if(error) - publishController.publishDisabled = false; - if(error.response) - { - publishController.errors.message = error.response.data.errors.message; - } - }); + publishController.errors.message = error.response.data.errors.message; + } + }); }, setData: function(event, blocktype, body) { diff --git a/system/author/js/vue-meta.js b/system/author/js/vue-meta.js index ccb6267..e02eea3 100644 --- a/system/author/js/vue-meta.js +++ b/system/author/js/vue-meta.js @@ -119,7 +119,7 @@ Vue.component('tab-meta', { }) let meta = new Vue({ - delimiters: ['${', '}'], + delimiters: ['${', '}'], el: '#metanav', data: function () { return { diff --git a/system/author/metatabs.yaml b/system/author/metatabs.yaml index d6583fa..21bf418 100644 --- a/system/author/metatabs.yaml +++ b/system/author/metatabs.yaml @@ -36,10 +36,10 @@ meta: label: Hide checkboxlabel: Hide page from navigation class: medium - nofollow: + noindex: type: checkbox - label: Nofollow - checkboxlabel: Add no-follow and exclude from sitemap + label: Noindex + checkboxlabel: Add noindex tag and exclude from sitemap class: medium fieldsetauthor: type: fieldset diff --git a/themes/cyanine/cyanine.yaml b/themes/cyanine/cyanine.yaml index b8f542b..ab0bbac 100644 --- a/themes/cyanine/cyanine.yaml +++ b/themes/cyanine/cyanine.yaml @@ -1,5 +1,5 @@ name: Cyanine Theme -version: 1.1.8 +version: 1.1.9 description: Cyanine is a modern and flexible multi-purpose theme and the standard theme for typemill. author: Trendschau homepage: https://trendschau.net @@ -138,6 +138,20 @@ forms: type: text label: Teaser 2 Label fieldsize: half + teaser3title: + type: text + label: Teaser 3 Title + teaser3text: + type: text + label: Teaser 3 Text + teaser3link: + type: text + label: Teaser 3 Link + fieldsize: half + teaser3label: + type: text + label: Teaser 3 Label + fieldsize: half landingpageContrast: type: fieldset diff --git a/themes/cyanine/home/landingpageTeaser.twig b/themes/cyanine/home/landingpageTeaser.twig index 8765012..7063ce8 100644 --- a/themes/cyanine/home/landingpageTeaser.twig +++ b/themes/cyanine/home/landingpageTeaser.twig @@ -1,16 +1,27 @@
-
-
-

{{ settings.themes.cyanine.teaser1title }}

-

{{ settings.themes.cyanine.teaser1text }}

- {{ settings.themes.cyanine.teaser1label }} -
-
-

{{ settings.themes.cyanine.teaser2title }}

-

{{ settings.themes.cyanine.teaser2text }}

- {{ settings.themes.cyanine.teaser2label }} -
+
+ {% if settings.themes.cyanine.teaser1title %} +
+

{{ settings.themes.cyanine.teaser1title }}

+

{{ settings.themes.cyanine.teaser1text }}

+ {{ settings.themes.cyanine.teaser1label }} +
+ {% endif %} + {% if settings.themes.cyanine.teaser2title %} +
+

{{ settings.themes.cyanine.teaser2title }}

+

{{ settings.themes.cyanine.teaser2text }}

+ {{ settings.themes.cyanine.teaser2label }} +
+ {% endif %} + {% if settings.themes.cyanine.teaser3title %} +
+

{{ settings.themes.cyanine.teaser3title }}

+

{{ settings.themes.cyanine.teaser3text }}

+ {{ settings.themes.cyanine.teaser3label }} +
+ {% endif %}
\ No newline at end of file diff --git a/themes/cyanine/layout.twig b/themes/cyanine/layout.twig index ff3c223..cf0b842 100644 --- a/themes/cyanine/layout.twig +++ b/themes/cyanine/layout.twig @@ -9,6 +9,10 @@ + + {% if metatabs.meta.noindex %} + + {% endif %} {% if favicon %}