From 14ea7c79959fdf7980e3150bb043713b7b9553f2 Mon Sep 17 00:00:00 2001 From: trendschau Date: Thu, 3 Jun 2021 13:33:12 +0200 Subject: [PATCH] Version 1.4.6: Change content, CSRF-notification, fix blox editor, alert for raw editor, cyanine optimizations --- content/00-welcome/01-write-content.md | 2 +- content/00-welcome/02-manage-access.md | 2 +- content/00-welcome/index.md | 2 +- readme.md | 32 ++-- system/Controllers/AuthController.php | 6 + system/Controllers/BlockApiController.php | 1 - system/Controllers/FormController.php | 9 +- system/Controllers/SettingsController.php | 44 ++++- system/Controllers/SetupController.php | 6 + system/Middleware/RestrictApiAccess.php | 8 +- system/author/css/style.css | 18 ++- system/author/editor/publish-controller.twig | 50 +++--- system/author/js/vue-blox.js | 159 ++++++++++++++----- system/author/js/vue-publishcontroller.js | 8 + system/system.php | 7 +- themes/cyanine/css/style.css | 4 +- themes/cyanine/cyanine.yaml | 28 +++- themes/cyanine/home/landingpageIntro.twig | 4 +- themes/cyanine/layout.twig | 9 +- themes/cyanine/partials/breadcrumb.twig | 4 +- themes/cyanine/partials/footer.twig | 4 +- themes/cyanine/partials/navigation.twig | 2 +- 22 files changed, 299 insertions(+), 110 deletions(-) diff --git a/content/00-welcome/01-write-content.md b/content/00-welcome/01-write-content.md index 983a6fc..e72f7d0 100644 --- a/content/00-welcome/01-write-content.md +++ b/content/00-welcome/01-write-content.md @@ -1,6 +1,6 @@ # Write Content -Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues). +Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages and structure your websites. The visual markdown editor will help you to create content in a wysiwyg mode. The publish bar gives you full control over the status of each page. Watch the following video tutorial to learn all the details. ![youtube-video](media/live/youtube-6i2-uv88gke.jpeg "click to load video"){#6I2-uV88GkE .youtube} diff --git a/content/00-welcome/02-manage-access.md b/content/00-welcome/02-manage-access.md index f4dc1cb..64c58e1 100644 --- a/content/00-welcome/02-manage-access.md +++ b/content/00-welcome/02-manage-access.md @@ -1,6 +1,6 @@ # Manage Access -Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the section "access rights". If you activate one of the features, then Typemill will use session cookies on all frontend pages. +Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the section "access rights". If you activate one of the features, then Typemill will use session cookies on all frontend pages. Learn all the details in the following video tutorial: ![youtube-video](media/live/youtube-uw-m-4g1kaa.jpeg "click to load video"){#UW_m-4g1kAA .youtube} diff --git a/content/00-welcome/index.md b/content/00-welcome/index.md index 3dc32d6..dcca197 100644 --- a/content/00-welcome/index.md +++ b/content/00-welcome/index.md @@ -1,4 +1,4 @@ # Welcome -Great that you give Typemill a try!! Typemill is a small open source cms and a project in work. You will probably miss some important features, but we are working hard to add everything that is needed for a handy and productive writing-system. +Great that you give Typemill a try!! Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues). diff --git a/readme.md b/readme.md index 8868e3c..0fbd9ce 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,14 @@ # About TYPEMILL -TYPEMILL is a small flat file cms created for editors and writers. It provides an author-friendly dashboard and a visual-block-editor for markdown based on vue.js. Use TYPEMILL for manuals, documentations, web-books and similar publications. The website http://typemill.net itself is an example for TYPEMILL. +TYPEMILL is a lightweight flat file cms for micro-publishers. You can use it for documentations, manuals, special interest websites, and any other information-driven web-project. You can also enhance Typemill with plugins and generate professional e-books in pdf-format with it. The website http://typemill.net runs with Typemill. ![TYPEMILL Screenshot](https://typemill.net/media/tm-demo.gif) ## Features * Website with markdown-files. -* Flexible drag & drop navigation. * Visual markdown editor (VUE.js) and raw markdown mode. +* Flexible drag & drop navigation. * Markdown extras with * table of contents (TOC) * tables @@ -22,8 +22,15 @@ TYPEMILL is a small flat file cms created for editors and writers. It provides a * System configurations. * User management. * Flexible form management with YAML-files. -* Plugins (with symfony event dispatcher). +* Flexible access rights. * Themes (with TWIG). +* Plugins (with symfony event dispatcher). + +Some plugin highlights are: + +* **Ebooks**: Generate one or many professional pdf books from your typemill website. +* **Register**: Let users register to your website and give them access to pro-content. +* **Subscribe** (in work): Sell subscriptions for premium content with traditional pdf-invoices. ## Requirements @@ -62,7 +69,7 @@ You can use your ftp-software for that. ## Setup -You will be redirected to the `/setup` page, please create an initial user and configure your system in the author panel. +If you visit your website first, then you will be redirected to the `/setup` page. Please create an initial user and configure your system in the author panel. ## Login @@ -91,39 +98,38 @@ TYPEMILL is published under MIT licence. Please check the licence of the include ## IMPORTANT: How to Contribute -Typemill is still in an early stage and contributions are highly welcome. Please follow these rules: +Contributions are highly welcome. Please follow these rules: * If you plan bigger changes, then please create an issue first so we can discuss it. * Fork the "DEVELOP" branch from typemill. Never use the master branch, because it is protected and only contains tested releases. * Do your changes. -* Before your request, please pull the recent develop branch again to get latest changes. -* Then make a pull request for the DEVELOP branch again. +* After that pull the recent develop branch again to get the latest changes. +* Then make a pull request for the DEVELOP branch. You can check the [roadmap for Typemill](https://github.com/typemill/typemill/issues/35) and scroll through the issues. I will mark issues in future that are easy to start with or where help is highly appreciated. Here are some contribution-ideas for non-coder: -* Share typemill with social media. -* Write about typemill. +* Share Typemill with social media. +* Write about Typemill. * Improve the documentation. * Find bugs and errors (open a new issue on github for it). * Describe some missing features and explain, why they are important for other users. -Some ideas for devs (please fork this repository make your changes and create a pull request): +Some ideas for devs: * Fix a bug. * Create or port a theme, especially for documentations, knowlegde bases or web-books. * Create a fancy plugin. -* An auto-update functionality for core system, plugins and themes is highly needed. +* An auto-update functionality for the core system, for plugins and for themes is highly welcome. * Improve the accessibility of html and css. -* Implement user roles and rights with RBAC or ACL. * Write autotests with Cypress. For hints, questions, problems and support, please open up a new issue on GitHub. ## Support -This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can support this project with a donation or simply hire me for implementations. +This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can support this project with a donation or simply [hire me](https://trendschau.net) for implementations. Donate: https://www.paypal.me/typemill diff --git a/system/Controllers/AuthController.php b/system/Controllers/AuthController.php index 8d8908c..206e83c 100644 --- a/system/Controllers/AuthController.php +++ b/system/Controllers/AuthController.php @@ -76,6 +76,12 @@ class AuthController extends Controller public function login(Request $request, Response $response) { + if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('auth.show')); + } + /* log user attemps to authenticate */ $yaml = new WriteYaml(); $logins = $yaml->getYaml('settings/users', '.logins'); diff --git a/system/Controllers/BlockApiController.php b/system/Controllers/BlockApiController.php index 371fb87..cd1bfd2 100644 --- a/system/Controllers/BlockApiController.php +++ b/system/Controllers/BlockApiController.php @@ -325,7 +325,6 @@ class BlockApiController extends ContentController { return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404); } - /* parse markdown-file to content-array, if title parse title. */ if($this->params['block_id'] == 0) diff --git a/system/Controllers/FormController.php b/system/Controllers/FormController.php index 0e87c3b..4e77827 100644 --- a/system/Controllers/FormController.php +++ b/system/Controllers/FormController.php @@ -14,12 +14,19 @@ class FormController extends Controller public function savePublicForm($request, $response, $args) { if($request->isPost()) - { + { $params = $request->getParams(); reset($params); $pluginName = key($params); $referer = $request->getHeader('HTTP_REFERER'); + # check csrf protection + if($request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout. Please try again.'); + return $response->withRedirect($referer[0]); + } + # simple bot check with honeypot if(isset($params[$pluginName]['personal-mail'])) { diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index 84f1ad8..7a02ab4 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -67,7 +67,14 @@ class SettingsController extends Controller public function saveSettings($request, $response, $args) { if($request->isPost()) - { + { + + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('settings.show')); + } + $settings = \Typemill\Settings::getUserSettings(); $defaultSettings = \Typemill\Settings::getDefaultSettings(); $params = $request->getParams(); @@ -378,7 +385,13 @@ class SettingsController extends Controller public function saveThemes($request, $response, $args) { if($request->isPost()) - { + { + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('themes.show')); + } + $userSettings = \Typemill\Settings::getUserSettings(); $params = $request->getParams(); $themeName = isset($params['theme']) ? $params['theme'] : false; @@ -465,6 +478,13 @@ class SettingsController extends Controller { if($request->isPost()) { + + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('plugins.show')); + } + $userSettings = \Typemill\Settings::getUserSettings(); $pluginSettings = array(); $userInput = $request->getParams(); @@ -764,6 +784,12 @@ class SettingsController extends Controller { if($request->isPost()) { + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('user.new')); + } + $params = $request->getParams(); $user = new User(); $validate = new Validation(); @@ -793,6 +819,12 @@ class SettingsController extends Controller if($request->isPost()) { + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + $params = $request->getParams(); $userdata = $params['user']; $user = new User(); @@ -886,7 +918,13 @@ class SettingsController extends Controller public function deleteUser($request, $response, $args) { if($request->isPost()) - { + { + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('user.account')); + } + $params = $request->getParams(); $validate = new Validation(); $user = new User(); diff --git a/system/Controllers/SetupController.php b/system/Controllers/SetupController.php index 5dd0564..02dafff 100644 --- a/system/Controllers/SetupController.php +++ b/system/Controllers/SetupController.php @@ -62,6 +62,12 @@ class SetupController extends Controller { if($request->isPost()) { + if( $request->getattribute('csrf_result') === false ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withRedirect($this->c->router->pathFor('setup.welcome')); + } + $params = $request->getParams(); $validate = new Validation(); $user = new User(); diff --git a/system/Middleware/RestrictApiAccess.php b/system/Middleware/RestrictApiAccess.php index 00adb99..3d76e08 100644 --- a/system/Middleware/RestrictApiAccess.php +++ b/system/Middleware/RestrictApiAccess.php @@ -19,8 +19,14 @@ class RestrictApiAccess { if(!isset($_SESSION['login']) || !isset($_SESSION['role'])) { - return $response->withJson(['errors' => ['access denied']], 403); + return $response->withJson(['data' => false, 'errors' => ['message' => 'You are probably logged out. Please login and try again.']], 403); } + + # check csrf protection + if( $request->getattribute('csrf_result') === false ) + { + return $response->withJson(array('data' => false, 'errors' => ['message' => 'The form has a timeout. Please reload the page and try again.']), 403); + } return $next($request, $response); } } \ No newline at end of file diff --git a/system/author/css/style.css b/system/author/css/style.css index 1202253..60883db 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -226,7 +226,7 @@ h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weigh } section{ background: #FFFFFF; - box-shadow: 0 0 4px #ddd; + box-shadow: 0 4px 4px #ddd; position: relative; margin-bottom: 40px; padding: 20px 5px 40px; @@ -949,7 +949,7 @@ header a.button{ position: absolute; width: 100%; margin-top: -35px; - z-index: 1; +/* z-index: 1; */ } .tab-button{ font-size: 0.9em; @@ -968,7 +968,7 @@ header a.button{ outline: none; } section.tab{ - z-index: -1; +/* z-index: -1; */ padding: 40px; } /******************** @@ -2358,16 +2358,18 @@ button.format-item.close:hover{ display: inline-block; margin-top: 15px; } -input.definitionTerm{ +.definitionTerm{ width: 29%; display: inline-block; vertical-align: top; +} +.definitionDescription{ + width: 60%; + display: inline-block; + vertical-align: top; background: #fff; } -textarea.definitionDescription{ - width: 60%; - display: inline-block; - vertical-align: top; +.definitionRow input, .definitionRow textarea{ background: #fff; } button.addDL, diff --git a/system/author/editor/publish-controller.twig b/system/author/editor/publish-controller.twig index 770fd60..07ce8a6 100644 --- a/system/author/editor/publish-controller.twig +++ b/system/author/editor/publish-controller.twig @@ -20,29 +20,37 @@ {% endif %} {% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %} {{ __('raw mode') }}{{ __('raw') }} - {{ __('visual mode') }}{{ __('visual') }} + {{ __('visual mode') }}{{ __('visual') }} {% endif %} - - \ No newline at end of file diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index 310655c..3232827 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -718,33 +718,60 @@ const codeComponent = Vue.component('code-component', { template: '
' + '' + '
' + + '
' + + '' + + '' + + '
' + '' + '
', data: function(){ return { - codeblock: '' + prefix: '```', + language: '', + codeblock: '', } }, mounted: function(){ this.$refs.markdown.focus(); if(this.compmarkdown) { - var codeblock = this.compmarkdown.replace("````\n", ""); - codeblock = codeblock.replace("```\n", ""); - codeblock = codeblock.replace("\n````", ""); - codeblock = codeblock.replace("\n```", ""); - codeblock = codeblock.replace("\n\n", "\n"); - this.codeblock = codeblock; + var codelines = this.compmarkdown.split(/\r\n|\n\r|\n|\r/); + var linelength = codelines.length; + var codeblock = ''; + + for(i=0;i' + - '
' + - '' + - '
' + - '' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '' + - '
' + - '', + '
' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '{{ \'add definition\'|translate }}' + + '
' + + '', mounted: function(){ if(this.compmarkdown) { - var definitionList = this.compmarkdown.replace("\r\n", "\n"); - definitionList = definitionList.replace("\r", "\n"); - definitionList = definitionList.split("\n\n"); + var definitionList = this.compmarkdown.replace("\r\n", "\n"); + definitionList = definitionList.replace("\r", "\n"); + definitionList = definitionList.split("\n\n"); for(var i=0; i < definitionList.length; i++) { - var definition = definitionList[i].split("\n"); - - var term = definition[0]; - var description = definition[1]; - var id = i; + var definitionBlock = definitionList[i].split("\n"); + var term = definitionBlock[0]; + var descriptions = []; + var description = false; - if(description && description.substring(0, 2) == ": ") + /* parse one or more descriptions */ + for(var y=0; y < definitionBlock.length; y++) { - this.definitionList.push({'term': term ,'description': description.substring(2), 'id': id}); + if(y == 0) + { + continue; + } + + if(definitionBlock[y].substring(0, 2) == ": ") + { + /* if there is another description in the loop, then push that first then start a new one */ + if(description) + { + descriptions.push(description); + } + var cleandefinition = definitionBlock[y].substr(1).trim(); + var description = cleandefinition; + } + else + { + description += "\n" + definitionBlock[y]; + } } + + if(description) + { + descriptions.push(description); + } + this.definitionList.push({'term': term ,'descriptions': descriptions, 'id': i}); } + console.info(this.definitionList); } else { - this.definitionList.push({'term': '', 'description': '', 'id': 0}); + this.addDefinition(); } }, methods: { @@ -1287,23 +1347,31 @@ const definitionComponent = Vue.component('definition-component', { { return false; }, - updateterm: function(event, dindex) + updateterm: function(event, dtindex) { - this.definitionList[dindex].term = event.target.value; + this.definitionList[dtindex].term = event.target.value; }, - updatedescription: function(event, dindex) + updatedescription: function(event, dtindex, ddindex) { - this.definitionList[dindex].description = event.target.value; + this.definitionList[dtindex].descriptions[ddindex] = event.target.value; }, addDefinition: function() { var id = this.definitionList.length; - this.definitionList.push({'term': '', 'description': '', 'id': id}); + this.definitionList.push({'term': '', 'descriptions': [''], 'id': id}); + }, + deleteDefinition: function(dtindex) + { + this.definitionList.splice(dtindex,1); this.updateMarkdown(); }, - deleteDefinition: function(dindex) + addItem: function(dtindex) { - this.definitionList.splice(dindex,1); + this.definitionList[dtindex].descriptions.push(''); + }, + deleteItem: function(dtindex,ddindex) + { + this.definitionList[dtindex].descriptions.splice(ddindex,1); this.updateMarkdown(); }, moveDefinition: function(evt) @@ -1312,11 +1380,18 @@ const definitionComponent = Vue.component('definition-component', { }, updateMarkdown: function() { - var length = this.definitionList.length; + var dllength = this.definitionList.length; var markdown = ''; - for(i = 0; i < length; i++) + + for(i = 0; i < dllength; i++) { - markdown = markdown + this.definitionList[i].term + "\n: " + this.definitionList[i].description + "\n\n"; + markdown = markdown + this.definitionList[i].term; + var ddlength = this.definitionList[i].descriptions.length; + for(y = 0; y < ddlength; y++) + { + markdown = markdown + "\n: " + this.definitionList[i].descriptions[y]; + } + markdown = markdown + "\n\n"; } this.$emit('updatedMarkdown', markdown); }, diff --git a/system/author/js/vue-publishcontroller.js b/system/author/js/vue-publishcontroller.js index b8f440c..891f8d7 100644 --- a/system/author/js/vue-publishcontroller.js +++ b/system/author/js/vue-publishcontroller.js @@ -193,6 +193,14 @@ let publishController = new Vue({ self.handleErrors(error); }); }, + checkUnsafedContent(event){ + console.info(this.draftDisabled); + if(!this.draftDisabled) + { + event.preventDefault(); + this.showModal('unsafed'); + } + }, showModal: function(type){ this.modalType = type; this.modalWindow = true; diff --git a/system/system.php b/system/system.php index 518744f..74f25a6 100644 --- a/system/system.php +++ b/system/system.php @@ -210,7 +210,12 @@ foreach($session_segments as $segment) { $guard = new \Slim\Csrf\Guard(); $guard->setPersistentTokenMode(true); - + $guard->setfailurecallable(function ($request, $response, $next) + { + $request = $request->withattribute("csrf_result", false); + return $next($request, $response); + }); + return $guard; }; diff --git a/themes/cyanine/css/style.css b/themes/cyanine/css/style.css index b18b7e2..cdec760 100644 --- a/themes/cyanine/css/style.css +++ b/themes/cyanine/css/style.css @@ -124,8 +124,10 @@ article a:hover, article a:focus, article a:active, footer a:hover, footer a:focus, footer a:active{ text-decoration: none; } -/* Fix background for hightlight plugin */ + +/* Fix background for hightlight plugin code.hljs{ background: transparent; } +*/ /* For definition list */ dt::after{ diff --git a/themes/cyanine/cyanine.yaml b/themes/cyanine/cyanine.yaml index 561d861..8a5d824 100644 --- a/themes/cyanine/cyanine.yaml +++ b/themes/cyanine/cyanine.yaml @@ -1,5 +1,5 @@ name: Cyanine Theme -version: 1.1.3 +version: 1.1.4 description: Cyanine is a modern and flexible multi-purpose theme and the standard theme for typemill. author: Trendschau homepage: https://trendschau.net @@ -243,18 +243,34 @@ forms: label: Text/label for edit link placeholder: edit on github - fieldsetChapters: + fieldsetNavigations: type: fieldset legend: Navigations and Chapters fields: - collapseNav: - type: checkbox - label: Collapse navigation - checkboxlabel: Collapse and expand navigation? chapnum: type: checkbox label: Chapter Numbers checkboxlabel: Show chapter numbers in navigation? + collapseNav: + type: checkbox + label: Collapse navigation + checkboxlabel: Collapse and expand navigation? + expand: + type: text + label: Label for expand button + placeholder: expand navigation + collapse: + type: text + label: Label for collapse button + placeholder: collapse navigation + next: + type: text + label: Label for next link + placeholder: next + previous: + type: text + label: Label for previous link + placeholder: previous fieldsetfooter: type: fieldset diff --git a/themes/cyanine/home/landingpageIntro.twig b/themes/cyanine/home/landingpageIntro.twig index 383abb7..7c0d7dd 100644 --- a/themes/cyanine/home/landingpageIntro.twig +++ b/themes/cyanine/home/landingpageIntro.twig @@ -2,7 +2,9 @@
- {% if settings.themes.cyanine.introTitle %} + {% if logo %} + + {% elseif settings.themes.cyanine.introTitle %}

{{ settings.themes.cyanine.introTitle }}

{% else %}

{{ title }}

diff --git a/themes/cyanine/layout.twig b/themes/cyanine/layout.twig index 0bd038e..64bb8a8 100644 --- a/themes/cyanine/layout.twig +++ b/themes/cyanine/layout.twig @@ -242,7 +242,10 @@ {% if settings.themes.cyanine.collapseNav %}