diff --git a/content/.yaml b/content/.yaml new file mode 100644 index 0000000000000000000000000000000000000000..604c82edd44d8a4e7b4393f8dbf746034a71b4c7 --- /dev/null +++ b/content/.yaml @@ -0,0 +1,8 @@ +meta: + title: Typemill + description: 'Typemill is a user-friendly and lightweight open source CMS for publishing text-works like prose, lyrics, manuals, documentations, studies and more. Just download and start.' + author: 'Sebastian Schürmanns' + created: '2020-04-27' + time: 14-20-18 + navtitle: null + modified: '2020-04-27' diff --git a/media/files/neue-datei.tx6t b/media/files/neue-datei.tx6t new file mode 100644 index 0000000000000000000000000000000000000000..01dba9165181b3c98434759f2395cc4785997393 --- /dev/null +++ b/media/files/neue-datei.tx6t @@ -0,0 +1,13 @@ +Hallo Frau Zarth, + +vielleicht sagt Ihnen als Leiterin Com & Tech die Fachseite CMSstash.de etwas? Wir behandeln ausschließlich CMS und bieten seit letzter Woche auch ein Dienstleisterverzeichnis an. Ist das interessant für Nexum als Tech-Partner von Magnolia, Typo3 und Co? + +Herzliche Grüße + + +Hallo Herr Timm, + +ich kenne unitb vor allem als Spezialist für AEM, Magnolia oder Drupal. Vielleicht ist für Ihre Agentur die wachsende Zielgruppe von CMSstash interessant? Wir bieten seit letzter Woche ein Dienstleisterverzeichnis an, in dem sich CMS-Experten präsentieren können. + +Herzliche Grüße +octet-stream \ No newline at end of file diff --git a/system/Controllers/ArticleApiController.php b/system/Controllers/ArticleApiController.php index 034260a13baa282ff3f69d1f5879c073d01c90c5..1031ab717c99549f7e9fa09fe24e7c4c62974e60 100644 --- a/system/Controllers/ArticleApiController.php +++ b/system/Controllers/ArticleApiController.php @@ -84,9 +84,9 @@ class ArticleApiController extends ContentController # dispatch event $page = ['content' => $this->content, 'meta' => $meta, 'item' => $this->item]; - $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page)); + $page = $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page))->getData(); - return $response->withJson(['success' => true, 'meta' => $meta], 200); + return $response->withJson(['success' => true, 'meta' => $page['meta']], 200); } else { diff --git a/system/Controllers/MediaApiController.php b/system/Controllers/MediaApiController.php index 5b0ec9b9919129ca79109fb96dd1dacb3664937d..0cccfde4308f30dd67b0dfce55274a9fc32205fc 100644 --- a/system/Controllers/MediaApiController.php +++ b/system/Controllers/MediaApiController.php @@ -108,6 +108,11 @@ class MediaApiController extends ContentController if($imageProcessor->createImage($this->params['image'], $this->params['name'], $this->settings['images'])) { + # publish image directly, used for example by image field for meta-tabs + if($this->params['publish']) + { + $imageProcessor->publishImage(); + } return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]); } diff --git a/system/Controllers/MetaApiController.php b/system/Controllers/MetaApiController.php index 30c6df949694720a4c31e2fdd4d7937bee33bd83..b05dffc31cba3b1860e16c3d55ecba84f49fa0c0 100644 --- a/system/Controllers/MetaApiController.php +++ b/system/Controllers/MetaApiController.php @@ -272,10 +272,10 @@ class MetaApiController extends ContentController } # add the new/edited metadata - $meta[$tab] = $metaInput; + $metaPage[$tab] = $metaInput; # store the metadata - $writeMeta->updateYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml', $meta); + $writeMeta->updateYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml', $metaPage); if($structure) { diff --git a/system/Models/ProcessImage.php b/system/Models/ProcessImage.php index e6a1d18f54ee080ec00260b923d227e7ec03a4e7..1f70938f877ffb03770330070b353a3d2852a000 100644 --- a/system/Models/ProcessImage.php +++ b/system/Models/ProcessImage.php @@ -333,6 +333,8 @@ class ProcessImage extends ProcessAssets # generate images from live folder to 'tmthumbs' $liveImages = scandir($this->liveFolder); + $result = false; + foreach ($liveImages as $key => $name) { if (!in_array($name, array(".",".."))) diff --git a/system/Models/Validation.php b/system/Models/Validation.php index d5ed19a9947ce474532e8266a683620fa35d7d6b..be6811023a1f430bb6090a82d227b33912b3876f 100644 --- a/system/Models/Validation.php +++ b/system/Models/Validation.php @@ -342,7 +342,7 @@ class Validation */ public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions, $skiprequired = NULL) - { + { $v = new Validator(array($fieldName => $fieldValue)); if(isset($fieldDefinitions['required']) && !$skiprequired) @@ -412,8 +412,16 @@ class Validation # $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u'); break; case "textarea": - $v->rule('noHTML', $fieldName); - $v->rule('lengthMax', $fieldName, 1000); + # it understands array, json, yaml + if(is_array($fieldValue)) + { + $v = $this->checkArray($fieldValue, $v); + } + else + { + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); + } break; case "paragraph": $v->rule('noHTML', $fieldName); @@ -422,9 +430,13 @@ class Validation case "password": $v->rule('lengthMax', $fieldName, 100); break; + case "image": + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); + break; default: $v->rule('lengthMax', $fieldName, 1000); - $v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u'); + $v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u'); } return $this->validationResult($v, $objectName); } @@ -435,6 +447,20 @@ class Validation * @param obj $v the validation object. * @return bool */ + + public function checkArray($arrayvalues, $v) + { + foreach($arrayvalues as $key => $value) + { + if(is_array($value)) + { + $this->checkArray($value, $v); + } + $v->rule('noHTML', $value); + $v->rule('lengthMax', $value, 1000); + } + return $v; + } public function validationResult($v, $name = false) { diff --git a/system/author/css/style.css b/system/author/css/style.css index b4f6c00a1b852fed4018477ebb0602916ff4556e..a4f02ffeec1e96ae7c9786c15ba728b929b303e9 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -141,7 +141,7 @@ a.tm-download::before{ width: 30px; height: 30px; line-height: 30px; - font-family: "Comic Sans MS",cursive,sans-serif; + font-family: Calibri, "Segoe UI", Roboto, Courier, Helvetica, -apple-system, BlinkMacSystemFont, sans-serif, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.3em; font-weight: 900; border: 2px solid #e0474c; diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index e7b4139b06b3c010d0f9b2e2e295aeedbf3723eb..d174419bbb06257fd3b1ad5383626ff793c69a0a 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -2073,7 +2073,7 @@ const medialib = Vue.component('medialib', { this.$parent.showmedialib = false; - this.$parent.updatemarkdown(imgmarkdown); + this.$parent.updatemarkdown(imgmarkdown, image.src_live); } if(this.parentcomponent == 'files') { @@ -2084,7 +2084,7 @@ const medialib = Vue.component('medialib', { this.$parent.showmedialib = false; - this.$parent.updatemarkdown(filemarkdown); + this.$parent.updatemarkdown(filemarkdown, image.src_live); } }, selectFile: function(file) @@ -2106,7 +2106,7 @@ const medialib = Vue.component('medialib', { this.$parent.showmedialib = false; - this.$parent.updatemarkdown(imgmarkdown); + this.$parent.updatemarkdown(imgmarkdown, file.url); } if(this.parentcomponent == 'files') { @@ -2117,7 +2117,7 @@ const medialib = Vue.component('medialib', { this.$parent.filemeta = true; this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')'; - this.$parent.updatemarkdown(filemarkdown); + this.$parent.updatemarkdown(filemarkdown, file.url); } this.showFiles(); }, diff --git a/system/author/js/vue-meta.js b/system/author/js/vue-meta.js index e5449975642479b2b2ccf64f6e1722fcc9b727ea..35c5f6be0e2b06775292edbcd4beeb5cc3425b1b 100644 --- a/system/author/js/vue-meta.js +++ b/system/author/js/vue-meta.js @@ -2,12 +2,12 @@ const FormBus = new Vue(); Vue.filter('translate', function (value) { if (!value) return '' - value = value.replace(/[ ]/g,"_").replace(/[.]/g, "_").replace(/[-]/g, "_").replace(/[,]/g,"_").replace(/[(]/g,"_").replace(/[)]/g,"_").toUpperCase() - translated_string = labels[value] + transvalue = value.replace(/[ ]/g,"_").replace(/[.]/g, "_").replace(/[-]/g, "_").replace(/[,]/g,"_").replace(/[(]/g,"_").replace(/[)]/g,"_").toUpperCase() + translated_string = labels[transvalue] if(!translated_string || translated_string.length === 0){ - return value + '?' + return value } else { - return labels[value] + return labels[transvalue] } }) @@ -37,18 +37,42 @@ Vue.component('component-text', { }, }) +Vue.component('component-hidden', { + props: ['class', 'id', 'maxlength', 'required', 'disabled', 'name', 'type', 'value', 'errors'], + template: '', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + Vue.component('component-textarea', { props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + data: function () { + return { + textareaclass: '' + } + }, template: '
' + '' + - '' + '{{ errors[name] }}' + '{{ description|translate }}' + @@ -58,6 +82,15 @@ Vue.component('component-textarea', { { FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); }, + formatValue: function(value) + { + if(value !== null && typeof value === 'object') + { + this.textareaclass = 'codearea'; + return JSON.stringify(value, undefined, 4); + } + return value; + }, }, }) diff --git a/system/author/js/vue-shared.js b/system/author/js/vue-shared.js new file mode 100644 index 0000000000000000000000000000000000000000..7d0944124a5f144016f104846ed7ce3cd62e724f --- /dev/null +++ b/system/author/js/vue-shared.js @@ -0,0 +1,136 @@ +Vue.component('component-image', { + props: ['class', 'id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '
' + + '' + + '
' + + '
' + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + ' ' + + '

upload an image

'+ + '
' + + '
' + + '' + + '
' + + '
' + + '' + + '
' + + '' + + '' + + '
' + + '
' + + '

{{ description|translate }}

' + + '
{{ errors[name] }}
' + + '
' + + '
' + + '' + + '' + + '' + + '
', + data: function(){ + return { + maxsize: 5, // megabyte + imgpreview: false, + showmedialib: false, + load: false, + } + }, + mounted: function(){ + this.imgpreview = this.value; + }, + methods: { + update: function(value) + { + FormBus.$emit('forminput', {'name' : this.name, 'value' : value}); + }, + updatemarkdown: function(markdown, url) + { + /* is called from child component medialib */ + this.update(url); + }, + deleteImage: function() + { + this.imgpreview = false; + this.update(''); + }, + openmedialib: function() + { + this.showmedialib = true; + }, + onFileChange: function( e ) + { + if(e.target.files.length > 0) + { + let imageFile = e.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + publishController.errors.message = "Only images are allowed."; + } + else if (size > this.maxsize) + { + publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; + } + else + { + sharedself = this; + + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(e) + { + sharedself.imgpreview = e.target.result; + + /* load image to server */ + var url = sharedself.$root.$data.root + '/api/v1/image'; + + var params = { + 'url': document.getElementById("path").value, + 'image': e.target.result, + 'name': imageFile.name, + 'publish': true, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }; + + var method = 'POST'; + + sendJson(function(response, httpStatus) + { + if(response) + { + var result = JSON.parse(response); + + if(result.errors) + { + publishController.errors.message = result.errors; + } + else + { + sharedself.update(result.name); + } + } + }, method, url, params); + } + } + } + } + }, +}) \ No newline at end of file diff --git a/system/author/layouts/layoutBlox.twig b/system/author/layouts/layoutBlox.twig index 7bc2e0e36653d011c290e43466b9db3ad24830f6..177fb41d425d058a85e0efe239167031a8bc05e7 100644 --- a/system/author/layouts/layoutBlox.twig +++ b/system/author/layouts/layoutBlox.twig @@ -216,6 +216,7 @@ + {{ assets.renderJS() }} diff --git a/system/author/metatabs.yaml b/system/author/metatabs.yaml index 1cc5f37b332ab83f2ece6b68dab40a326b60dbea..391ef6ae4f0ff4e47b65a9175eaf56f7bd3f97d5 100644 --- a/system/author/metatabs.yaml +++ b/system/author/metatabs.yaml @@ -11,6 +11,13 @@ meta: size: 160 class: large description: If not filled, the description is extracted from content. + heroimage: + type: image + label: Hero Image + description: Maximum size for an image is 5 MB. Hero images are not supported by all themes. + heroimagealt: + type: text + label: Alternative Text for the hero image author: type: text label: author diff --git a/system/author/partials/fields.twig b/system/author/partials/fields.twig index 41b229b927f6bba2036c2eb7e16f0a67c7642469..3dc7227d9989a10fb8b46d61617e50672a9dea9b 100644 --- a/system/author/partials/fields.twig +++ b/system/author/partials/fields.twig @@ -9,6 +9,11 @@ + {% elseif field.type == 'image' %} +
+ +

upload an image

+
{% elseif field.type == 'paragraph' %} {{ markdown(field.getContent()) }} diff --git a/themes/typemill/css/style.css b/themes/typemill/css/style.css index 0a603a91b2c2cc78264c8ea807d1da54b9df32d3..3db2d27a4aee729d87d3c678089ead769f51ae18 100644 --- a/themes/typemill/css/style.css +++ b/themes/typemill/css/style.css @@ -605,7 +605,7 @@ a.tm-download::before{ width: 30px; height: 30px; line-height: 30px; - font-family: "Comic Sans MS",cursive,sans-serif; + font-family: Calibri, "Segoe UI", Roboto, Courier, Helvetica, -apple-system, BlinkMacSystemFont, sans-serif, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.3em; font-weight: 900; border: 2px solid #e0474c; diff --git a/themes/typemill/typemill.yaml b/themes/typemill/typemill.yaml index 9a1cd56dbf76d26d27fb7008ded10eac3f0768b7..15db73a3296f5a0e5d7686aebdf78efd31fc2ed5 100644 --- a/themes/typemill/typemill.yaml +++ b/themes/typemill/typemill.yaml @@ -27,6 +27,10 @@ forms: label: Different Design for Startpage checkboxlabel: Activate Special Startpage-Design + test: + type: image + label: Image test + coverlogo: type: checkbox label: Logo on startpage