diff --git a/cache/lastCache.txt b/cache/lastCache.txt index 4d74b8e..5654e3b 100644 --- a/cache/lastCache.txt +++ b/cache/lastCache.txt @@ -1 +1 @@ -1571726903 \ No newline at end of file +1572791939 \ No newline at end of file diff --git a/composer.json b/composer.json index de6e23e..c28ee25 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "trendschau/typemill", + "name": "typemill/typemill", "type": "project", "description": "A crazy simple tool to create web-documentations and online manuals with markdown files.", "keywords": ["documentations","manuals","flat-file","Markdown","php"], - "homepage": "http://typemill.net", + "homepage": "https://typemill.net", "license": "MIT", "config": { "vendor-dir": "system/vendor" diff --git a/content/00-Welcome/03-Markdown-Test.md b/content/00-Welcome/03-Markdown-Test.md index a4b0883..9fc7200 100644 --- a/content/00-Welcome/03-Markdown-Test.md +++ b/content/00-Welcome/03-Markdown-Test.md @@ -299,8 +299,6 @@ $$ x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a) $$ -Das war es dann aber auch. - [^1]: Thank you for scrolling. [^2]: This is the end of the page. diff --git a/readme.md b/readme.md index 35f5dce..0f37b1b 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,7 @@ TYPEMILL is a small flat file cms created for editors and writers. It provides a * Allows super easy backend and frontend forms with simple YAML-files. * Ships with a fully responsive standard theme * Ships with plugins for + * Search * MathJax and KaTeX. * Code highlighting. * Matomo/Piwik and Google Analytics. @@ -60,7 +61,7 @@ You can use your ftp-software for that. ## Setup -Please go to `your-typemill-website.com/setup`, create an initial user and then setup your system in the author panel. +Please go to `your-typemill-website.com/setup`, create an initial user and configure your system in the author panel. ## Login @@ -88,17 +89,20 @@ Typemill is still in an early stage and contributions are highly welcome. Here a Some ideas for devs (please fork this repository make your changes and create a pull request): * Fix a bug. -* Create a nice theme. -* Create a new plugin. -* Improve the CSS-code with BEM or utility-css (e.g. Tailwind) and make it modular. -* Rebuild the theme with the new css-grid feature. +* Create a theme. +* Create a plugin. +* Auto-update functionality for core system plugins and themes. +* Create a plugin and theme download page. * Improve the accessibility of html and css. -* Help to establish autotests with selenium or cypress. -* Write unit-tests. -* Write an auto-update functionality. +* Implement an ACL for user roles and rights. For hints, questions, problems and support, please open up a new issue on GitHub. ## Licence -TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too. \ No newline at end of file +TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too. + +## Community & Supporters + +* [Eziquel Bruni](https://github.com/EzequielBruni) edits the typemill documentation. +* [vodaris](https://www.vodaris.de) sponsored the development of the search plugin. \ No newline at end of file diff --git a/system/Controllers/ContentApiController.php b/system/Controllers/ContentApiController.php index 490bd8b..28a09f2 100644 --- a/system/Controllers/ContentApiController.php +++ b/system/Controllers/ContentApiController.php @@ -87,7 +87,7 @@ class ContentApiController extends ContentController } public function unpublishArticle(Request $request, Response $response, $args) - { + { # get params from call $this->params = $request->getParams(); $this->uri = $request->getUri(); @@ -129,6 +129,19 @@ class ContentApiController extends ContentController } } + # check if it is a folder and if the folder has published pages. + $message = false; + if($this->item->elementType == 'folder') + { + foreach($this->item->folderContent as $folderContent) + { + if($folderContent->status == 'published') + { + $message = 'There are published pages within this folder. The pages are not visible on your website anymore.'; + } + } + } + # update the file $delete = $this->deleteContentFiles(['md']); @@ -143,7 +156,7 @@ class ContentApiController extends ContentController # dispatch event $this->c->dispatcher->dispatch('onPageUnpublished', new OnPageUnpublished($this->item)); - return $response->withJson(['success'], 200); + return $response->withJson(['success' => ['message' => $message]], 200); } else { diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index 4509fa3..214bc83 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -45,7 +45,7 @@ class SettingsController extends Controller $params = $request->getParams(); $newSettings = isset($params['settings']) ? $params['settings'] : false; $validate = new Validation(); - + if($newSettings) { /* make sure only allowed fields are stored */ @@ -54,6 +54,7 @@ class SettingsController extends Controller 'author' => $newSettings['author'], 'copyright' => $newSettings['copyright'], 'year' => $newSettings['year'], + 'language' => $newSettings['language'], 'startpage' => isset($newSettings['startpage']) ? true : false, 'editor' => $newSettings['editor'], ); @@ -562,6 +563,13 @@ class SettingsController extends Controller if($validate->username($params['username'])) { $user->deleteUser($params['username']); + + # if user deleted his own account + if($_SESSION['user'] == $params['username']) + { + session_destroy(); + return $response->withRedirect($this->c->router->pathFor('auth.show')); + } $this->c->flash->addMessage('info', 'Say goodbye, the user is gone!'); return $response->withRedirect($this->c->router->pathFor('user.list')); diff --git a/system/Controllers/SetupController.php b/system/Controllers/SetupController.php index dfe7ef5..00ebcf0 100644 --- a/system/Controllers/SetupController.php +++ b/system/Controllers/SetupController.php @@ -15,16 +15,36 @@ class SetupController extends Controller $checkFolder = new Write(); $systemcheck = array(); - + + # check folders and create them if possible try{ $checkFolder->checkPath('settings'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); } try{ $checkFolder->checkPath('settings/users'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); } try{ $checkFolder->checkPath('content'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); } try{ $checkFolder->checkPath('cache'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); } try{ $checkFolder->checkPath('media'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); } + + # check php-version + if (version_compare(phpversion(), '7.0.0', '<')) { + $systemcheck['error'][] = 'The PHP-version of your server is ' . phpversion() . ' and Typemill needs at least 7.0.0'; + } + + # check if mod rewrite is enabled + $modules = apache_get_modules(); + if(!in_array('mod_rewrite', $modules)) + { + $systemcheck['error'][] = 'The apache module "mod_rewrite" is not enabled.'; + } + + # check if GD extension is enabled + if(!extension_loaded('gd')){ + $systemcheck['error'][] = 'The php-extension GD for image manipulation is not enabled.'; + } + + $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' => $systemcheck )); + return $this->render($response, 'auth/setup.twig', array( 'messages' => $setuperror, 'systemcheck' => $systemcheck )); } public function create($request, $response, $args) diff --git a/system/author/auth/setup.twig b/system/author/auth/setup.twig index 405ff67..4aaacf8 100644 --- a/system/author/auth/setup.twig +++ b/system/author/auth/setup.twig @@ -4,9 +4,19 @@ {% block content %}
+ + {% if systemcheck %} +

Missing Requirements

+ + {% endif %} +
- +
diff --git a/system/author/auth/welcome.twig b/system/author/auth/welcome.twig index 7c5e8a6..ffbf5c0 100644 --- a/system/author/auth/welcome.twig +++ b/system/author/auth/welcome.twig @@ -11,7 +11,7 @@

Hurra!

Your account has been created and you are logged in now.

Next step: Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.

-

New: Typemill ships with a new search plugin now. Just activate the plugin and enjoy!!

+

New:Not sure how to add strong, emphasis and inline-code with Markdown? We have buttons for that now!!

Get help: If you have any questions, please consult the docs or open a new issue on github.

Configure your website diff --git a/system/author/css/style.css b/system/author/css/style.css index 5d159c6..eafc0a6 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -1660,6 +1660,49 @@ button.format-item.close:hover{ border: 1px solid #cc4146; } +/************************ +** INLINE FORMATG BAR ** +************************/ + +/* format menu */ +.inlineFormatBar { + height: 30px; + padding: 5px 10px; + background: #333; + border-radius: 3px; + position: absolute; + top: 0; + left: 0; + transform: translate(-50%, -100%); + transition: 0.2s all; + display: flex; + justify-content: center; + align-items: center; +} +/* Triangle below format popup */ +.inlineFormatBar:after { + content: ''; + position: absolute; + left: 50%; + bottom: -5px; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid #333; +} +.inlineFormatItem { + color: #FFF; + cursor: pointer; +} +.inlineFormatItem:hover { + color: #1199ff; +} +.inlineFormatItem + .inlineFormatItem { + margin-left: 10px; +} + /************************ ** BLOX EDITOR CONTENT ** ************************/ diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index a96ec3c..14c58da 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -78,7 +78,7 @@ const contentComponent = Vue.component('content-block', { this.compmarkdown = $event; this.$nextTick(function () { this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px'; - }); + }); }, switchToEditMode: function() { @@ -86,7 +86,7 @@ const contentComponent = Vue.component('content-block', { eventBus.$emit('closeComponents'); self = this; self.$root.$data.freeze = true; /* freeze the data */ - self.$root.$data.sortdisabled = true; /* disable sorting */ + self.$root.$data.sortdisabled = true; /* disable sorting */ this.preview = 'hidden'; /* hide the html-preview */ this.edit = true; /* show the edit-mode */ this.compmarkdown = self.$root.$data.blockMarkdown; /* get markdown data */ @@ -392,6 +392,123 @@ const contentComponent = Vue.component('content-block', { }, }) +const inlineFormatsComponent = Vue.component('inline-formats', { + template: '
' + + '' + + '' + + '' + + '
', + data: function(){ + return { + formatBar: false, + startX: 0, + startY: 0, + x: 0, + y: 0, + textComponent: '', + selectedText: '', + startPos: false, + endPos: false, + showInlineFormat: false, + } + }, + mounted: function() { + this.formatBar = document.getElementById('formatBar'); + window.addEventListener('mouseup', this.onMouseup), + window.addEventListener('mousedown', this.onMousedown) + }, + beforeDestroy: function() { + window.removeEventListener('mouseup', this.onMouseup), + window.removeEventListener('mousedown', this.onMousedown) + }, + computed: { + highlightableEl () { + return this.$slots.default[0].elm + } + }, + methods: { + onMousedown: function(event) { + this.startX = event.offsetX; + this.startY = event.offsetY; + }, + onMouseup: function(event) { + + /* if click is on format popup */ + if(this.formatBar.contains(event.target)) + { + return; + } + + /* if click is outside the textarea */ + if(!this.highlightableEl.contains(event.target)) + { + this.showInlineFormat = false; + return; + } + + this.textComponent = document.getElementsByClassName("mdcontent")[0]; + + /* grab the selected text */ + if (document.selection != undefined) + { + this.textComponent.focus(); + var sel = document.selection.createRange(); + selectedText = sel.text; + } + /* Mozilla version */ + else if (this.textComponent.selectionStart != undefined) + { + this.startPos = this.textComponent.selectionStart; + this.endPos = this.textComponent.selectionEnd; + selectedText = this.textComponent.value.substring(this.startPos, this.endPos) + } + + var trimmedSelection = selectedText.replace(/\s/g, ''); + + if(trimmedSelection.length == 0) + { + this.showInlineFormat = false; + return; + } + + /* determine the width of selection to position the format bar */ + if(event.offsetX > this.startX) + { + var width = event.offsetX - this.startX; + this.x = event.offsetX - (width/2); + } + else + { + var width = this.startX - event.offsetX; + this.x = event.offsetX + (width/2); + } + + this.y = event.offsetY - 15; + + this.showInlineFormat = true; + this.selectedText = selectedText; + }, + formatBold() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '**' + this.selectedText + '**' + content.substring(this.endPos, content.length); + this.$parent.updatemarkdown(content); + }, + formatItalic() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '_' + this.selectedText + '_' + content.substring(this.endPos, content.length); + this.$parent.updatemarkdown(content); + }, + formatCode() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '`' + this.selectedText + '`' + content.substring(this.endPos, content.length); + this.$parent.updatemarkdown(content); + } + } +}) + const titleComponent = Vue.component('title-component', { props: ['compmarkdown', 'disabled'], template: '
', @@ -411,16 +528,18 @@ const markdownComponent = Vue.component('markdown-component', { props: ['compmarkdown', 'disabled'], template: '
' + '
' + - '' + - '
', + '' + + '' + + '' + + '
', mounted: function(){ this.$refs.markdown.focus(); autosize(document.querySelectorAll('textarea')); }, methods: { - updatemarkdown: function(event) + updatemarkdown: function(value) { - this.$emit('updatedMarkdown', event.target.value); + this.$emit('updatedMarkdown', value); }, }, }) @@ -509,8 +628,10 @@ const quoteComponent = Vue.component('quote-component', { template: '
' + '' + '
' + - '' + - '
', + '' + + '' + + '' + + '
', data: function(){ return { quote: '' @@ -521,7 +642,7 @@ const quoteComponent = Vue.component('quote-component', { if(this.compmarkdown) { var quote = this.compmarkdown.replace("> ", ""); - quote = this.compmarkdown.replace(">", ""); + quote = this.compmarkdown.replace(">", "").trim(); this.quote = quote; } this.$nextTick(function () { @@ -529,14 +650,10 @@ const quoteComponent = Vue.component('quote-component', { }); }, methods: { - createmarkdown: function(event) - { - this.quote = event.target.value; - var quote = '> ' + event.target.value; - this.updatemarkdown(quote); - }, - updatemarkdown: function(quote) + updatemarkdown: function(value) { + this.quote = value; + var quote = '> ' + value; this.$emit('updatedMarkdown', quote); }, }, @@ -546,7 +663,9 @@ const ulistComponent = Vue.component('ulist-component', { props: ['compmarkdown', 'disabled'], template: '
' + '
' + - '' + + '' + + '' + + '' + '
', mounted: function(){ this.$refs.markdown.focus(); @@ -581,9 +700,9 @@ const ulistComponent = Vue.component('ulist-component', { }); }, methods: { - updatemarkdown: function(event) + updatemarkdown: function(value) { - this.$emit('updatedMarkdown', event.target.value); + this.$emit('updatedMarkdown', value); }, }, }) @@ -592,7 +711,9 @@ const olistComponent = Vue.component('olist-component', { props: ['compmarkdown', 'disabled'], template: '
' + '
' + - '' + + '' + + '' + + '' + '
', mounted: function(){ this.$refs.markdown.focus(); @@ -605,9 +726,9 @@ const olistComponent = Vue.component('olist-component', { }); }, methods: { - updatemarkdown: function(event) + updatemarkdown: function(value) { - this.$emit('updatedMarkdown', event.target.value); + this.$emit('updatedMarkdown', value); }, }, }) @@ -911,7 +1032,7 @@ const definitionComponent = Vue.component('definition-component', { template: '
' + '
' + '' + - '
' + + '
' + '' + '' + '' + @@ -1337,6 +1458,7 @@ let editor = new Vue({ 'table-component': tableComponent, 'definition-component': definitionComponent, 'math-component': mathComponent, + 'inline-formats' : inlineFormatsComponent, }, data: { root: document.getElementById("main").dataset.url, @@ -1580,7 +1702,7 @@ let editor = new Vue({ renderMathInElement(document.getElementById("blox-"+elementid)); }); } - if (typeof MathJax !== false) { + if (typeof MathJax !== 'undefined') { self.$nextTick(function () { MathJax.Hub.Queue(["Typeset",MathJax.Hub,"blox-"+elementid]); }); diff --git a/system/author/js/vue-publishcontroller.js b/system/author/js/vue-publishcontroller.js index e2bdb5f..5abf110 100644 --- a/system/author/js/vue-publishcontroller.js +++ b/system/author/js/vue-publishcontroller.js @@ -203,24 +203,24 @@ let publishController = new Vue({ self.publishResult = "fail"; self.errors.message = "You are probably logged out. Please backup your changes, login and then try again." } - else if(httpStatus != 200) - { - self.publishDisabled = false; - self.publishResult = "fail"; - self.errors.message = "Something went wrong, please refresh the page and try again." - } else if(response) { var result = JSON.parse(response); + self.modalWindow = false; + + if(httpStatus != 200) + { + self.publishDisabled = false; + self.publishResult = "fail"; + self.errors.message = "Something went wrong, please refresh the page and try again."; + } if(result.errors) { - self.modalWindow = "modal"; if(result.errors.message){ self.errors.message = result.errors.message }; } else if(result.url) { - self.modalWindow = "modal"; window.location.replace(result.url); } } diff --git a/system/author/settings/system.twig b/system/author/settings/system.twig index 1390ebe..275557b 100644 --- a/system/author/settings/system.twig +++ b/system/author/settings/system.twig @@ -3,6 +3,8 @@ {% set startpage = old.settings.startpage ? old.settings.startpage : settings.startpage %} {% set linebreaks = old.settings.linebreaks ? old.settings.linebreaks : settings.linebreaks %} {% set year = settings.year ? settings.year : "now"|date("Y") %} +{% set mylang = settings.language ? settings.language : locale %} +{% set mycopy = settings.copyright ? settings.copyright : "@" %} {% block content %} @@ -36,7 +38,7 @@ {% if errors.settings.copyright %} @@ -52,7 +54,7 @@ {% if errors.settings.language %} diff --git a/system/author/settings/themes.twig b/system/author/settings/themes.twig index 4bde9ef..edf9d18 100644 --- a/system/author/settings/themes.twig +++ b/system/author/settings/themes.twig @@ -14,7 +14,7 @@ {% for themeName, theme in themes %} - +
@@ -67,10 +67,10 @@
- +
- +