diff --git a/system/Controllers/MetaApiController.php b/system/Controllers/MetaApiController.php index 271d9a4..ef55b19 100644 --- a/system/Controllers/MetaApiController.php +++ b/system/Controllers/MetaApiController.php @@ -30,10 +30,13 @@ class MetaApiController extends ContentController # loop through all plugins foreach($this->settings['plugins'] as $name => $plugin) { - $pluginSettings = \Typemill\Settings::getObjectSettings('plugins', $name); - if($pluginSettings && isset($pluginSettings['metatabs'])) + if($plugin['active']) { - $metatabs = array_merge_recursive($metatabs, $pluginSettings['metatabs']); + $pluginSettings = \Typemill\Settings::getObjectSettings('plugins', $name); + if($pluginSettings && isset($pluginSettings['metatabs'])) + { + $metatabs = array_merge_recursive($metatabs, $pluginSettings['metatabs']); + } } } @@ -155,4 +158,6 @@ class MetaApiController extends ContentController # return with the new metadata return $response->withJson(array('metadata' => $metaData, 'errors' => false)); } -} \ No newline at end of file +} + +# check models -> writeYaml for getPageMeta and getPageMetaDefaults. \ No newline at end of file diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index d09a334..2d4b9df 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -301,7 +301,7 @@ class SettingsController extends Controller { /* validate the user-input */ $this->validateInput('plugins', $pluginName, $userInput[$pluginName], $validate); - + /* use the input data */ $pluginSettings[$pluginName] = $userInput[$pluginName]; } @@ -364,6 +364,13 @@ class SettingsController extends Controller $originalFields['recaptcha_secretkey'] = ['type' => 'text', 'label' => 'Recaptcha Secret Key', 'help' => 'Add the recaptcha secret key here. You can get the key from the recaptcha website.', 'description' => 'The secret key is mandatory if you activate the recaptcha field']; } + # if plugin is not active, then skip required + $skiprequired = false; + if($objectType == 'plugins' && !isset($userInput['active'])) + { + $skiprequired = true; + } + /* take the user input data and iterate over all fields and values */ foreach($userInput as $fieldName => $fieldValue) { @@ -373,7 +380,7 @@ class SettingsController extends Controller if($fieldDefinition) { /* validate user input for this field */ - $validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition); + $validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition, $skiprequired); } if(!$fieldDefinition && $fieldName != 'active') { @@ -463,7 +470,7 @@ class SettingsController extends Controller if($validate->newUser($params, $userroles)) { - $userdata = array('username' => $params['username'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']); + $userdata = array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']); $user->createUser($userdata); @@ -511,7 +518,7 @@ class SettingsController extends Controller if($validate->existingUser($params, $userroles)) { - $userdata = array('username' => $params['username'], 'email' => $params['email'], 'userrole' => $params['userrole']); + $userdata = array('username' => $params['username'], 'firstname' => $params['firstname'], 'lastname' => $params['lastname'], 'email' => $params['email'], 'userrole' => $params['userrole']); if(empty($params['password']) AND empty($params['newpassword'])) { diff --git a/system/Models/Field.php b/system/Models/Field.php index ad481c2..712a1ea 100644 --- a/system/Models/Field.php +++ b/system/Models/Field.php @@ -64,6 +64,7 @@ class Field 'id', 'autocomplete', 'placeholder', + 'maxlength', 'size', 'rows', 'cols', diff --git a/system/Models/Folder.php b/system/Models/Folder.php index 7c4b3fd..036ba62 100644 --- a/system/Models/Folder.php +++ b/system/Models/Folder.php @@ -53,8 +53,15 @@ class Folder { if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item)) { - $subFolder = $item; - $folderContent[$subFolder] = self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft); + + $subFolder = $item; + $folderPublished = file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md'); + + # scan that folder only if it is a draft or if the folder is published (contains index.md) + if($draft OR $folderPublished) + { + $folderContent[$subFolder] = self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft); + } } else { diff --git a/system/Models/User.php b/system/Models/User.php index ded5290..66be810 100644 --- a/system/Models/User.php +++ b/system/Models/User.php @@ -38,6 +38,15 @@ class User extends WriteYaml 'password' => $this->generatePassword($params['password']), 'userrole' => $params['userrole'] ); + + if(isset($params['firstname'])) + { + $userdata['firstname'] = $params['firstname']; + } + if(isset($params['lastname'])) + { + $userdata['lastname'] = $params['lastname']; + } if($this->updateYaml('settings/users', $userdata['username'] . '.yaml', $userdata)) { @@ -58,8 +67,20 @@ class User extends WriteYaml $update = array_merge($userdata, $params); $this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update); + + $_SESSION['user'] = $update['username']; + $_SESSION['role'] = $update['userrole']; + + if(isset($update['firstname'])) + { + $_SESSION['firstname'] = $update['firstname']; + } + if(isset($update['lastname'])) + { + $_SESSION['lastname'] = $update['lastname']; + } - return $userdata['username']; + return $userdata['username']; } public function deleteUser($username) @@ -88,6 +109,15 @@ class User extends WriteYaml $_SESSION['user'] = $user['username']; $_SESSION['role'] = $user['userrole']; $_SESSION['login'] = $user['lastlogin']; + + if(isset($user['firstname'])) + { + $_SESSION['firstname'] = $user['firstname']; + } + if(isset($user['lastname'])) + { + $_SESSION['lastname'] = $user['lastname']; + } } } diff --git a/system/Models/Validation.php b/system/Models/Validation.php index 989fff9..bcea4ca 100644 --- a/system/Models/Validation.php +++ b/system/Models/Validation.php @@ -124,6 +124,10 @@ class Validation $v->rule('lengthBetween', 'password', 5, 20)->message("Length between 5 - 20"); $v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20"); $v->rule('userAvailable', 'username')->message("User already exists"); + $v->rule('noHTML', 'firstname')->message(" contains HTML"); + $v->rule('lengthBetween', 'firstname', 2, 40); + $v->rule('noHTML', 'lastname')->message(" contains HTML"); + $v->rule('lengthBetween', 'lastname', 2, 40); $v->rule('email', 'email')->message("e-mail is invalid"); $v->rule('in', 'userrole', $userroles); @@ -137,10 +141,14 @@ class Validation $v->rule('alphaNum', 'username')->message("invalid"); $v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20"); $v->rule('userExists', 'username')->message("user does not exist"); + $v->rule('noHTML', 'firstname')->message(" contains HTML"); + $v->rule('lengthBetween', 'firstname', 2, 40); + $v->rule('noHTML', 'lastname')->message(" contains HTML"); + $v->rule('lengthBetween', 'lastname', 2, 40); $v->rule('email', 'email')->message("e-mail is invalid"); $v->rule('in', 'userrole', $userroles); - return $this->validationResult($v); + return $this->validationResult($v); } public function username($username) @@ -321,15 +329,31 @@ class Validation * @return obj $v the validation object passed to a result method. */ - public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions) + public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions, $skiprequired = NULL) { $v = new Validator(array($fieldName => $fieldValue)); - - if(isset($fieldDefinitions['required'])) + + if(isset($fieldDefinitions['required']) && !$skiprequired) { $v->rule('required', $fieldName); } - + if(isset($fieldDefinitions['maxlength'])) + { + $v->rule('lengthMax', $fieldName, $fieldDefinitions['maxlength']); + } + if(isset($fieldDefinitions['max'])) + { + $v->rule('max', $fieldName, $fieldDefinitions['max']); + } + if(isset($fieldDefinitions['min'])) + { + $v->rule('min', $fieldName, $fieldDefinitions['min']); + } + if(isset($fieldDefinitions['pattern'])) + { + $v->rule('regex', $fieldName, '/^' . $fieldDefinitions['pattern'] . '$/'); + } + switch($fieldDefinitions['type']) { case "select": @@ -350,7 +374,7 @@ class Validation { $v->rule('in', $key, $options); } - break; + break; case "color": $v->rule('regex', $fieldName, '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/'); break; @@ -361,33 +385,35 @@ class Validation $v->rule('date', $fieldName); break; case "checkbox": - $v->rule('accepted', $fieldName); + if(isset($fieldDefinitions['required'])) + { + $v->rule('accepted', $fieldName); + } break; case "url": - $v->rule('lengthMax', $fieldName, 200); $v->rule('url', $fieldName); + $v->rule('lengthMax', $fieldName, 200); break; case "text": - $v->rule('lengthMax', $fieldName, 200); + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 500); $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u'); break; case "textarea": - $v->rule('lengthMax', $fieldName, 1000); $v->rule('noHTML', $fieldName); - // $v->rule('regex', $fieldName, '/<[^<]+>/'); + $v->rule('lengthMax', $fieldName, 1000); break; case "paragraph": - $v->rule('lengthMax', $fieldName, 1000); $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); break; case "password": $v->rule('lengthMax', $fieldName, 100); 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); } diff --git a/system/Models/WriteYaml.php b/system/Models/WriteYaml.php index 7364220..0254a2b 100644 --- a/system/Models/WriteYaml.php +++ b/system/Models/WriteYaml.php @@ -86,12 +86,27 @@ class WriteYaml extends Write $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' => $settings['author'], # change to session, extend userdata + 'author' => $author, + 'created' => date("Y-m-d"), ] ]; diff --git a/system/author/auth/welcome.twig b/system/author/auth/welcome.twig index 6cc77e2..faa6ede 100644 --- a/system/author/auth/welcome.twig +++ b/system/author/auth/welcome.twig @@ -11,7 +11,7 @@ <h1>Hurra!</h1> <p>Your account has been created and you are logged in now.</p> <p><strong>Next step:</strong> Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.</p> - <p><strong>New:</strong>Hurra! Version 1.3.0 is out and now you can edit meta-information like title and description.</p> + <p><strong>New:</strong>Hurra! Series 1.3 is out and now you can edit meta-information like title and description.</p> <p><strong>Get help:</strong> If you have any questions, please consult the <a target="_blank" href="https://typemill.net/typemill"><i class="icon-link-ext"></i> docs</a> or open a new issue on <a target="_blank" href="https://github.com/typemill/typemill"><i class="icon-link-ext"></i> github</a>.</p> </div> <a class="button" href="{{ path_for('settings.show') }}">Configure your website</a> diff --git a/system/author/css/style.css b/system/author/css/style.css index 8d8027a..1d77906 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -1773,7 +1773,6 @@ button.format-item.close:hover{ top: 0; left: 0; transform: translate(-50%, -100%); - transition: 0.2s all; display: flex; justify-content: center; align-items: center; @@ -1809,7 +1808,7 @@ button.format-item.close:hover{ margin-right: 2px; } .urlinput{ - width: 80%; + width: 75%; min-height: auto; background: #555; color: #fff; diff --git a/system/author/editor/editor-raw.twig b/system/author/editor/editor-raw.twig index db69474..22a8d8d 100644 --- a/system/author/editor/editor-raw.twig +++ b/system/author/editor/editor-raw.twig @@ -5,6 +5,27 @@ <div class="formWrapper"> + <div id="metanav" class="metanav" v-cloak> + + <button + v-for="tab in tabs" + v-bind:key="tab" + v-bind:class="['tab-button', { active: currentTab === tab }]" + v-on:click="currentTab = tab" + >${tab}</button> + + <component + class="tab" + v-bind:is="currentTabComponent" + :saved="saved" + :errors="formErrors[currentTab]" + :schema="formDefinitions[currentTab]" + :formdata="formData[currentTab]" + v-on:saveform="saveForm"> + </component> + + </div> + <div id="editor" class="editor"> <form action="#" v-cloak> diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index d6688de..0e0d7ad 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -393,7 +393,7 @@ const contentComponent = Vue.component('content-block', { }) const inlineFormatsComponent = Vue.component('inline-formats', { - template: '<div><div :style="{ left: `${x}px`, top: `${y}px` }" @mousedown.prevent="" v-show="showInlineFormat" id="formatBar" class="inlineFormatBar">' + + template: '<div><div :style="{ left: `${x}px`, top: `${y}px`, width: `${z}px` }" @mousedown.prevent="" v-show="showInlineFormat" id="formatBar" class="inlineFormatBar">' + '<div v-if="link">' + '<input v-model="url" @keyup.13="formatLink" ref="urlinput" class="urlinput" type="text" placeholder="insert url">' + '<span class="inlineFormatItem inlineFormatLink" @mousedown.prevent="formatLink"><svg class="icon icon-check"><use xlink:href="#icon-check"></use></svg></span>' + @@ -410,10 +410,12 @@ const inlineFormatsComponent = Vue.component('inline-formats', { data: function(){ return { formatBar: false, + formatElements: 0, startX: 0, startY: 0, x: 0, y: 0, + z: 150, textComponent: '', selectedText: '', startPos: false, @@ -500,6 +502,10 @@ const inlineFormatsComponent = Vue.component('inline-formats', { this.y = event.offsetY - 15; + /* calculate the width of the format bar */ + this.formatElements = document.getElementsByClassName('inlineFormatItem').length; + this.z = this.formatElements * 30; + this.showInlineFormat = true; this.selectedText = selectedText; }, @@ -508,30 +514,35 @@ const inlineFormatsComponent = Vue.component('inline-formats', { content = this.textComponent.value; content = content.substring(0, this.startPos) + '**' + this.selectedText + '**' + content.substring(this.endPos, content.length); this.$parent.updatemarkdown(content); + this.showInlineFormat = false; }, formatItalic() { content = this.textComponent.value; content = content.substring(0, this.startPos) + '_' + this.selectedText + '_' + content.substring(this.endPos, content.length); this.$parent.updatemarkdown(content); + this.showInlineFormat = false; }, formatCode() { content = this.textComponent.value; content = content.substring(0, this.startPos) + '`' + this.selectedText + '`' + content.substring(this.endPos, content.length); - this.$parent.updatemarkdown(content); + this.$parent.updatemarkdown(content); + this.showInlineFormat = false; }, formatMath() { content = this.textComponent.value; content = content.substring(0, this.startPos) + '$' + this.selectedText + '$' + content.substring(this.endPos, content.length); this.$parent.updatemarkdown(content); + this.showInlineFormat = false; }, formatLink() { if(this.url == "") { - this.link = false; + this.link = false; + this.showInlineFormat = false; return; } content = this.textComponent.value; @@ -543,11 +554,15 @@ const inlineFormatsComponent = Vue.component('inline-formats', { openLink() { this.link = true; + this.url = ''; + this.z = 200; this.$nextTick(() => this.$refs.urlinput.focus()); }, closeLink() { this.link = false; + this.url = ''; + this.showInlineFormat = false; } } }) diff --git a/system/author/js/vue-meta.js b/system/author/js/vue-meta.js index 340877c..b55a452 100644 --- a/system/author/js/vue-meta.js +++ b/system/author/js/vue-meta.js @@ -1,26 +1,21 @@ const FormBus = new Vue(); Vue.component('component-text', { - props: ['class', 'placeholder', 'label', 'name', 'type', 'size', 'value', 'errors'], + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], template: '<div class="large">' + '<label>{{ label }}</label>' + - '<input type="text" :name="name" :placeholder="placeholder" :value="value" @input="update($event, name)">' + - '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + - '</div>', - methods: { - update: function($event, name) - { - FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); - }, - }, -}) - -Vue.component('component-date', { - props: ['class', 'placeholder', 'readonly', 'label', 'name', 'type', 'size', 'value', 'errors'], - template: '<div class="large">' + - '<label>{{ label }}</label>' + - '<input type="date" :readonly="readonly" :name="name" :placeholder="placeholder" :value="value" @input="update($event, name)">' + + '<input type="text"' + + ' :id="id"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + '</div>', methods: { update: function($event, name) @@ -31,11 +26,195 @@ Vue.component('component-date', { }) Vue.component('component-textarea', { - props: ['class', 'placeholder', 'label', 'name', 'type', 'size', 'value', 'errors'], + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], template: '<div class="large">' + - '<label>{{label}}</label>' + - '<textarea :name="name" v-model="value" @input="update($event, name)"></textarea>' + + '<label>{{ label }}</label>' + + '<textarea ' + + ' :id="id"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + ' @input="update($event, name)"></textarea>' + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-url', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="url"' + + ' :id="id"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-number', { + props: ['class', 'id', 'description', 'min', 'max', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="number"' + + ' :id="id"' + + ' :min="min"' + + ' :min="max"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-email', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="email"' + + ' :id="id"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-tel', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="tel"' + + ' :id="id"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-password', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="password"' + + ' :id="id"' + + ' :maxlength="maxlength"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + '@input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-date', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="date" ' + + ' :id="id"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + ' @input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, name) + { + FormBus.$emit('forminput', {'name': name, 'value' : $event.target.value}); + }, + }, +}) + +Vue.component('component-color', { + props: ['class', 'id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<input type="color" ' + + ' :id="id"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :placeholder="placeholder"' + + ' :value="value"' + + ' @input="update($event, name)">' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + '</div>', methods: { update: function($event, name) @@ -46,13 +225,20 @@ Vue.component('component-textarea', { }) Vue.component('component-select', { - props: ['class', 'placeholder', 'label', 'name', 'type', 'size', 'options', 'value', 'errors'], + props: ['class', 'id', 'description', 'readonly', 'required', 'disabled', 'label', 'name', 'type', 'options', 'value', 'errors'], template: '<div class="large">' + '<label>{{label}}</label>' + - '<select v-model="value" @change="update($event,name)">' + - '<option v-for="option,optionkey in options" v-bind:value="optionkey">{{option}}</option>' + + '<select' + + ' :id="id"' + + ' :name="name"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' v-model="value"' + + ' @change="update($event,name)">' + + '<option v-for="option,optionkey in options" v-bind:value="optionkey">{{option}}</option>' + '</select>' + - '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + '</div>', methods: { update: function($event, name) @@ -63,13 +249,21 @@ Vue.component('component-select', { }) Vue.component('component-checkbox', { - props: ['class', 'label', 'checkboxlabel', 'name', 'type', 'value', 'errors'], + props: ['class', 'id', 'description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'name', 'type', 'value', 'errors'], template: '<div class="large">' + '<label>{{ label }}</label>' + '<label class="control-group">{{ checkboxlabel }}' + - '<input type="checkbox" :name="name" v-model="value" @change="update($event, value, name)">' + + '<input type="checkbox"' + + ' :id="id"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' v-model="value"' + + ' @change="update($event, value, name)">' + '<span class="checkmark"></span>' + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + '</label>' + '</div>', methods: { @@ -80,14 +274,50 @@ Vue.component('component-checkbox', { }, }) +Vue.component('component-checkboxlist', { + props: ['class', 'description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'options', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + + '<label>{{ label }}</label>' + + '<label v-for="option, optionvalue in options" class="control-group">{{ option }}' + + '<input type="checkbox"' + + ' :id="optionvalue"' + + ' :value="optionvalue"' + + ' v-model="value" ' + + ' @change="update($event, value, optionvalue, name)">' + + '<span class="checkmark"></span>' + + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + + '</div>', + methods: { + update: function($event, value, optionvalue, name) + { + /* if value (array) for checkboxlist is not initialized yet */ + if(value === true || value === false) + { + value = [optionvalue]; + } + FormBus.$emit('forminput', {'name': name, 'value' : value}); + }, + }, +}) + Vue.component('component-radio', { - props: ['label', 'options', 'name', 'type', 'value', 'errors'], - template: '<div class="medium">' + + props: ['class', 'id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'value', 'errors'], + template: '<div class="large">' + '<label>{{ label }}</label>' + '<label v-for="option,optionvalue in options" class="control-group">{{ option }}' + - '<input type="radio" :name="name" :value="optionvalue" v-model="value" @change="update($event, value, name)">' + + '<input type="radio"' + + ' :id="id"' + + ' :readonly="readonly"' + + ' :required="required"' + + ' :disabled="disabled"' + + ' :name="name"' + + ' :value="optionvalue"' + + ' v-model="value" ' + + ' @change="update($event, value, name)">' + '<span class="radiomark"></span>' + '<span v-if="errors[name]" class="error">{{ errors[name] }}</span>' + + '<span v-else class="fielddescription"><small>{{ description }}</small></span>' + '</label>' + '</div>', methods: { @@ -167,12 +397,14 @@ let meta = new Vue({ .then(function (response) { var formdefinitions = response.data.metadefinitions; + for (var key in formdefinitions) { if (formdefinitions.hasOwnProperty(key)) { self.tabs.push(key); self.formErrors[key] = false; } } + self.formErrorsReset = self.formErrors; self.formDefinitions = formdefinitions; diff --git a/system/author/layouts/layoutEditor.twig b/system/author/layouts/layoutEditor.twig index 50750e1..6070f31 100644 --- a/system/author/layouts/layoutEditor.twig +++ b/system/author/layouts/layoutEditor.twig @@ -20,6 +20,9 @@ <link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" /> <link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20191231" /> <link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" /> + + {{ assets.renderCSS() }} + </head> <body> <svg style="position: absolute; width: 0; height: 0; overflow: hidden" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> @@ -56,13 +59,25 @@ </article> <footer></footer> </div> + <script src="{{ base_url }}/system/author/js/axios.min.js?20191124"></script> + <script> + const myaxios = axios.create(); + myaxios.defaults.baseURL = "{{ base_url }}"; + </script> <script src="{{ base_url }}/system/author/js/vue.min.js?20191231"></script> <script src="{{ base_url }}/system/author/js/autosize.min.js?20191231"></script> <script src="{{ base_url }}/system/author/js/sortable.min.js?20191231"></script> <script src="{{ base_url }}/system/author/js/vuedraggable.umd.min.js?20191231"></script> <script src="{{ base_url }}/system/author/js/author.js?20191231"></script> + + {{ assets.renderEditorJS() }} + <script src="{{ base_url }}/system/author/js/vue-publishcontroller.js?20191231"></script> <script src="{{ base_url }}/system/author/js/vue-editor.js?20191231"></script> + <script src="{{ base_url }}/system/author/js/vue-meta.js?20191231"></script> <script src="{{ base_url }}/system/author/js/vue-navi.js?20191231"></script> + + {{ assets.renderJS() }} + </body> </html> \ No newline at end of file diff --git a/system/author/metatabs.yaml b/system/author/metatabs.yaml index bfa68b7..4928300 100644 --- a/system/author/metatabs.yaml +++ b/system/author/metatabs.yaml @@ -3,19 +3,30 @@ meta: title: type: text label: Meta title - size: 60 + maxlength: 60 class: large description: type: textarea label: Meta description size: 160 class: large + description: If not filled, the description is extracted from content. author: type: text label: author class: large + description: Taken from your user account if set. + manualdate: + type: date + label: Manual date modified: type: date - label: Last modified at (readonly) + label: Last modified live (readonly) readonly: readonly - class: large \ No newline at end of file + class: medium + description: Used as fallback when no manual date is set. + created: + type: date + label: Created at (readonly) + readonly: readonly + class: medium \ No newline at end of file diff --git a/system/author/settings/plugins.twig b/system/author/settings/plugins.twig index b747801..8644c83 100644 --- a/system/author/settings/plugins.twig +++ b/system/author/settings/plugins.twig @@ -6,7 +6,7 @@ <div class="formWrapper"> - <form method="POST" action="{{ path_for('plugins.save') }}"> + <form method="POST" action="{{ path_for('plugins.save') }}" novalidate> <section id="plugins" class="plugins"> diff --git a/system/author/settings/user.twig b/system/author/settings/user.twig index a6740a7..391267a 100644 --- a/system/author/settings/user.twig +++ b/system/author/settings/user.twig @@ -23,6 +23,22 @@ <span class="error">{{ errors.username | first }}</span> {% endif %} </div> + + <div class="large{{ errors.firstname ? ' errors' : '' }}"> + <label for="firstname">First Name</label> + <input type="text" name="firstname" value="{{ old.firstname ? old.firstname : userdata.firstname }}"> + {% if errors.firstname %} + <span class="error">{{ errors.firstname | first }}</span> + {% endif %} + </div> + + <div class="large{{ errors.lastname ? ' errors' : '' }}"> + <label for="lastname">Last Name</label> + <input type="text" name="lastname" value="{{ old.lastname ? old.lastname : userdata.lastname }}"> + {% if errors.lastname %} + <span class="error">{{ errors.lastname | first }}</span> + {% endif %} + </div> <div class="large{{ errors.email ? ' errors' : '' }}"> <label for="email">E-Mail <abbr title="required">*</abbr></label> @@ -48,7 +64,7 @@ <div class="large{{ errors.password ? ' errors' : '' }}"> <label for="password">Actual Password</label> - <input type="password" name="password"> + <input type="password" name="password" autocomplete="off"> {% if errors.password %} <span class="error">{{ errors.password | first }}</span> {% endif %} @@ -56,7 +72,7 @@ <div class="large{{ errors.newpassword ? ' errors' : '' }}"> <label for="newpassword">New Password</label> - <input type="password" name="newpassword"> + <input type="password" name="newpassword" autocomplete="off"> {% if errors.newpassword %} <span class="error">{{ errors.newpassword | first }}</span> {% endif %} diff --git a/system/author/settings/usernew.twig b/system/author/settings/usernew.twig index 585becb..0e103b9 100644 --- a/system/author/settings/usernew.twig +++ b/system/author/settings/usernew.twig @@ -22,7 +22,23 @@ <span class="error">{{ errors.username | first }}</span> {% endif %} </div> - + + <div class="large{{ errors.firstname ? ' errors' : '' }}"> + <label for="firstname">First Name</label> + <input type="text" name="firstname" value="{{ old.firstname ? old.firstname : userdata.firstname }}"> + {% if errors.firstname %} + <span class="error">{{ errors.firstname | first }}</span> + {% endif %} + </div> + + <div class="large{{ errors.lastname ? ' errors' : '' }}"> + <label for="lastname">Last Name</label> + <input type="text" name="lastname" value="{{ old.lastname ? old.lastname : userdata.lastname }}"> + {% if errors.lastname %} + <span class="error">{{ errors.lastname | first }}</span> + {% endif %} + </div> + <div class="large{{ errors.email ? ' errors' : '' }}"> <label for="email">E-Mail <abbr title="required">*</abbr></label> <input type="text" name="email" value="{{ old.email ? old.email : '' }}" required> @@ -45,7 +61,7 @@ <div class="large{{ errors.password ? ' errors' : '' }}"> <label for="password">Password <abbr title="required">*</abbr></label> - <input type="password" name="password" required> + <input type="password" name="password" autocomplete="off" required> {% if errors.password %} <span class="error">{{ errors.password | first }}</span> {% endif %} diff --git a/themes/typemill/page.twig b/themes/typemill/page.twig index f8a83fb..ea9cb64 100644 --- a/themes/typemill/page.twig +++ b/themes/typemill/page.twig @@ -1,3 +1,5 @@ +{% set published = metatabs.meta.manualdate ? metatabs.meta.manualdate : metatabs.meta.modified %} + {% if content is empty %} <h1>{{ item.name }}</h1> @@ -9,10 +11,10 @@ {% if (settings.themes.typemill.socialPosition.top or settings.themes.typemill.modifiedPosition.top or settings.themes.typemill.authorPosition.top or settings.themes.typemill.gitPosition.top) %} <div class="meta-info"> {% if settings.themes.typemill.authorPosition.top %} - <small>{{ settings.themes.typemill.authorIntro }}: {{ settings.author }}</small> + <small>{{ settings.themes.typemill.authorIntro }}: {{ metatabs.meta.author|default(settings.author) }}</small> {% endif %} {% if settings.themes.typemill.modifiedPosition.top %} - <small>{{ settings.themes.typemill.modifiedText }}: {{ metatabs.meta.modified|date(settings.themes.typemill.modifiedFormat) }}</small> + <small>{{ settings.themes.typemill.modifiedText }}: {{ published|date(settings.themes.typemill.modifiedFormat) }}</small> {% endif %} {% if settings.themes.typemill.socialPosition.top %} <div id="share-icons" class="share-icons hide"> @@ -34,10 +36,10 @@ {% if (settings.themes.typemill.socialPosition.bottom or settings.themes.typemill.modifiedPosition.bottom or settings.themes.typemill.authorPosition.bottom or settings.themes.typemill.gitPosition.bottom) %} <div class="meta-info"> {% if settings.themes.typemill.authorPosition.bottom %} - <small>{{ settings.themes.typemill.authorIntro }}: {{ settings.author }}</small> + <small>{{ settings.themes.typemill.authorIntro }}: {{ metatabs.meta.author|default(settings.author) }}</small> {% endif %} {% if settings.themes.typemill.modifiedPosition.bottom %} - <small>{{ settings.themes.typemill.modifiedText }}: {{ metatabs.meta.modified|date(settings.themes.typemill.modifiedFormat) }}</small> + <small>{{ settings.themes.typemill.modifiedText }}: {{ published|date(settings.themes.typemill.modifiedFormat) }}</small> {% endif %} {% if settings.themes.typemill.socialPosition.bottom %} <div id="share-icons-bottom" class="share-icons hide"> diff --git a/themes/typemill/partials/layout.twig b/themes/typemill/partials/layout.twig index 97b9fef..30dfb3d 100644 --- a/themes/typemill/partials/layout.twig +++ b/themes/typemill/partials/layout.twig @@ -69,6 +69,7 @@ <script src="{{ base_url }}/themes/typemill/js/script.js"></script> <script src="{{ base_url }}/system/author/js/lazy-video.js?20190602"></script> <script>typemillUtilities.start();</script> + {{ assets.renderJS() }} {% endblock %} diff --git a/themes/typemill/typemill.yaml b/themes/typemill/typemill.yaml index b47023d..8596546 100644 --- a/themes/typemill/typemill.yaml +++ b/themes/typemill/typemill.yaml @@ -1,5 +1,5 @@ name: Typemill Theme -version: 1.2.0 +version: 1.2.1 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