Browse Source

Version 1.4.1: Custom Fields half ready

trendschau 4 years ago
parent
commit
2282ab7fb8

+ 29 - 0
plugins/adamhall/adamhall.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Plugins\Adamhall;
+
+use Typemill\Plugin;
+
+class Adamhall extends Plugin
+{
+    public static function getSubscribedEvents()
+    {
+        return array(
+            'onTwigLoaded'      => 'onTwigLoaded',
+            'onMetaLoaded'      => 'onMetaLoaded'
+        );
+    }
+
+    public function onTwigLoaded()
+    {
+        $this->addEditorJS('/adamhall/js/adamhall.js');
+    }
+
+    public function onMetaLoaded($meta)
+    {
+        $meta = $meta->getData();
+
+        # do something with the fields:
+        $myTabInformation = $meta['mytab'];
+    }
+}

+ 15 - 0
plugins/adamhall/adamhall.yaml

@@ -0,0 +1,15 @@
+name: Adam Hall Plugin
+version: 1.0.0
+description: Plugin for Adam Hall that integrates Custom Fields
+author: Sebastian Schürmanns
+homepage: http://trendschau.net
+licence: Owned by Adam Hall
+
+metatabs:
+  adamhall:
+    fields:
+      myfield:
+        type: customfields
+        label: Simple Custom Field
+        description: Please add some custom fields
+        data: array

+ 26 - 0
plugins/adamhall/js/adamhall.js

@@ -0,0 +1,26 @@
+Vue.component('tab-adamhall', {
+	props: ['saved', 'errors', 'formdata', 'schema'],
+	template: '<section><form>' +
+				'<component v-for="(field, index) in schema.fields"' +
+            	    ':key="index"' +
+                	':is="selectComponent(field)"' +
+                	':errors="errors"' +
+                	':name="index"' +
+                	'v-model="formdata[index]"' +
+                	'v-bind="field">' +
+				'</component>' + 
+				'<div v-if="saved" class="metaLarge"><div class="metaSuccess">Saved successfully</div></div>' +
+				'<div v-if="errors" class="metaLarge"><div class="metaErrors">Please correct the errors above</div></div>' +
+				'<div class="large"><input type="submit" @click.prevent="saveInput" value="save"></input></div>' +
+			  '</form></section>',
+	methods: {
+		selectComponent: function(field)
+		{
+			return 'component-'+field.type;
+		},
+		saveInput: function()
+		{
+  			this.$emit('saveform');
+		},
+	}
+})

+ 37 - 0
system/Controllers/MetaApiController.php

@@ -122,6 +122,28 @@ class MetaApiController extends ContentController
 			{
 			{
 				$metascheme[$tabname][$fieldname] = true;
 				$metascheme[$tabname][$fieldname] = true;
 				$metadata[$tabname][$fieldname] = isset($pagemeta[$tabname][$fieldname]) ? $pagemeta[$tabname][$fieldname] : null;
 				$metadata[$tabname][$fieldname] = isset($pagemeta[$tabname][$fieldname]) ? $pagemeta[$tabname][$fieldname] : null;
+
+				# special treatment for customfields
+				if(isset($fielddefinitions['type']) && ($fielddefinitions['type'] == 'customfields' ) )
+				{
+					# loop through the customdata
+					foreach($metadata[$tabname][$fieldname] as $key => $value)
+					{
+						# and make sure that arrays are transformed back into strings
+						if(isset($value['value']) && is_array($value['value']))
+						{
+							$valuestring = implode('\n',$value['value']);
+							$metadata[$tabname][$fieldname][$key]['value'] = $valuestring;
+						}
+					}
+					/*
+					echo 'fielddefinition: <pre>';
+					print_r($fielddefinitions);
+					echo '</pre>metadata: <pre>';
+					print_r($pagemeta[$tabname][$fieldname]);
+					die();
+					*/
+				}
 			}
 			}
 		}
 		}
 
 
@@ -208,6 +230,21 @@ class MetaApiController extends ContentController
 				{
 				{
 					$errors[$tab][$fieldName] = $result[$fieldName][0];
 					$errors[$tab][$fieldName] = $result[$fieldName][0];
 				}
 				}
+
+				# special treatment for customfields 
+				if($fieldDefinition && isset($fieldDefinition['type']) && ($fieldDefinition['type'] == 'customfields' ) && isset($fieldDefinition['data']) && ($fieldDefinition['data'] == 'array' )  )
+				{
+					foreach($fieldValue as $key => $valuePair)
+					{
+						if(isset($valuePair['value']))
+						{
+							$arrayValues = explode(PHP_EOL,$valuePair['value']);
+							echo '<pre>';
+							print_r($arrayValues);
+						}
+					}
+					die();
+				}	
 			}
 			}
 		}
 		}
 
 

+ 21 - 0
system/Models/Validation.php

@@ -63,6 +63,23 @@ class Validation
 			return true;
 			return true;
 		}, 'contains one or more invalid ip-adress');
 		}, 'contains one or more invalid ip-adress');
 
 
+		Validator::addRule('customfields', function($field, $value, array $params, array $fields) use ($user)
+		{
+			foreach($value as $customfield)
+			{
+				if(!isset($customfield['key']) OR empty($customfield['key']) OR (preg_match('/^([a-z0-9])+$/i', $customfield['key']) == false) )
+				{
+		        	return false;
+		        }
+
+				if (!isset($customfield['value']) OR empty($customfield['value']) OR ( $customfield['value'] != strip_tags($customfield['value']) ) )
+				{
+					return false;
+				}
+			}
+			return true;
+		}, 'contains one or more invalid values');
+
 		Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user)
 		Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user)
 		{
 		{
 			$userdata = $user->getUser($fields['username']);
 			$userdata = $user->getUser($fields['username']);
@@ -459,6 +476,10 @@ class Validation
 				$v->rule('lengthMax', $fieldName, 1000);
 				$v->rule('lengthMax', $fieldName, 1000);
 				$v->rule('image_types', $fieldName);
 				$v->rule('image_types', $fieldName);
 				break;
 				break;
+			case "customfields":
+				$v->rule('array', $fieldName);
+				$v->rule('customfields', $fieldName);
+				break;			
 			default:
 			default:
 				$v->rule('lengthMax', $fieldName, 1000);
 				$v->rule('lengthMax', $fieldName, 1000);
 				$v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u');
 				$v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u');

+ 13 - 0
system/author/css/style.css

@@ -128,6 +128,9 @@ input.upload{
   opacity: 0;
   opacity: 0;
 }
 }
 
 
+/* fix wrong text-align/family */
+input, textarea, button {font-family: arial, sans-serif}
+
 /****************************
 /****************************
 *  	download-commponent		*
 *  	download-commponent		*
 ****************************/
 ****************************/
@@ -1280,6 +1283,16 @@ span.error{
 	background: #70c1b3;
 	background: #70c1b3;
 }
 }
 
 
+/********************
+*	Customfields	*
+********************/
+
+.customkey{
+	width: 29%;
+}
+.customvalue{
+	width: 60%;
+}
 
 
 /********************
 /********************
 *	UPDATE-BANNER	*
 *	UPDATE-BANNER	*

+ 5 - 5
system/author/js/vue-blox.js

@@ -1248,13 +1248,13 @@ const definitionComponent = Vue.component('definition-component', {
 	template: '<div class="definitionList">' +
 	template: '<div class="definitionList">' +
 				'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
 				'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
 				'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
 				'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
-  			    '<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
+	  			    '<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
 						'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
 						'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
 						'<input type="text" class="definitionTerm" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dindex)" @blur="updateMarkdown">' +
 						'<input type="text" class="definitionTerm" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dindex)" @blur="updateMarkdown">' +
-		  		  '<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' + 
-	  			  '<textarea class="definitionDescription" v-bind:placeholder="\'description\'|translate" v-html="definition.description" :disabled="disabled" @input="updatedescription($event, dindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
-					  '<button class="delDL" @click.prevent="deleteDefinition(dindex)"><svg class="icon icon-minus"><use xlink:href="#icon-minus"></use></svg></button>' +
-				  '</div>' +
+			  		  	'<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' + 
+		  			  	'<textarea class="definitionDescription" v-bind:placeholder="\'description\'|translate" v-html="definition.description" :disabled="disabled" @input="updatedescription($event, dindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
+						'<button class="delDL" @click.prevent="deleteDefinition(dindex)"><svg class="icon icon-minus"><use xlink:href="#icon-minus"></use></svg></button>' +
+					'</div>' +
 				'</draggable>' +
 				'</draggable>' +
 				'<button class="addDL" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg> {{ \'add definition\'|translate }}</button>' +
 				'<button class="addDL" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg> {{ \'add definition\'|translate }}</button>' +
 				'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
 				'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +

+ 96 - 0
system/author/js/vue-meta.js

@@ -332,6 +332,7 @@ Vue.component('component-checkboxlist', {
 			  	  '<span class="checkmark"></span>' +
 			  	  '<span class="checkmark"></span>' +
 			  	  '<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|translate }}</small></span>' +
 			  	  '<span v-else class="fielddescription"><small>{{ description|translate }}</small></span>' +
+			  	'</label>' +
 			  '</div>',
 			  '</div>',
 	methods: {
 	methods: {
 		update: function($event, value, optionvalue, name)
 		update: function($event, value, optionvalue, name)
@@ -373,6 +374,92 @@ Vue.component('component-radio', {
 	},
 	},
 })
 })
 
 
+Vue.component('component-customfields', {
+	props: ['class', 'id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'value', 'errors'],
+	data: function () {
+		return {
+			fielderrors: false,
+			fielddetails: {},
+		 }
+	},
+
+	template: '<div class="large">' +
+				'<label class="mb2">{{ label|translate }}</label>' +
+			  	'<div v-if="fielderrors" class="error mb2"><small>{{ fielderrors }}</small></div>' +
+			  	'<div v-else="description" class="fielddescription mb2"><small>{{ description|translate }}</small></div>' +
+	  			'<div class="customrow flex items-start mb3" v-for="(pairobject, pairindex) in value">' +
+					'<input type="text" placeholder="key" class="customkey" :class="pairobject.keyerror" :value="pairobject.key" @input="updatePairKey(pairindex,$event)">' +
+			  		'<div class="mt3"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' + 
+	  			  	'<textarea placeholder="value" class="customvalue pa3" :class="pairobject.valueerror" v-html="pairobject.value" @input="updatePairValue(pairindex,$event)"></textarea>' +
+					'<button class="bg-tm-red white bn ml2 h1 w2 br1" @click.prevent="deleteField(pairindex)"><svg class="icon icon-minus"><use xlink:href="#icon-minus"></use></svg></button>' +
+				'</div>' +
+				'<button class="bg-tm-green white bn br1 pa2 f6" @click.prevent="addField()"><svg class="icon icon-plus f7"><use xlink:href="#icon-plus"></use></svg> Add Fields</button>' +
+			  '</div>',
+	mounted: function(){
+		
+		if(this.value === null)
+		{
+			this.value = [{}];
+		}
+	},
+	methods: {
+		update: function(value, name)
+		{
+			FormBus.$emit('forminput', {'name': name, 'value': value});
+		},
+		updatePairKey: function(index,event)
+		{
+			this.value[index].key = event.target.value;
+			
+			var regex = /^[a-z0-9]+$/i;
+			if(regex.test(event.target.value))
+			{
+				this.fielderrors = false;
+				delete this.value[index].keyerror;
+				this.update(this.value,this.name);
+			}
+			else
+			{
+				this.value[index].keyerror = 'red';
+				this.fielderrors = 'Only alphanumeric for keys allowed';
+			}
+		},
+		updatePairValue: function(index, event)
+		{
+			this.value[index].value = event.target.value;
+			
+			var regex = /<.*(?=>)/gm;
+			if(event.target.value == '' || regex.test(event.target.value))
+			{
+				this.value[index].valueerror = 'red';
+				this.fielderrors = 'No empty values or html tags are allowed';				
+			}
+			else
+			{
+				this.fielderrors = false;
+				delete this.value[index].valueerror;
+				this.update(this.value,this.name);
+			}
+		},
+		addField: function()
+		{
+			for(object in this.value)
+			{
+				if(Object.keys(this.value[object]).length === 0)
+				{
+					return;
+				}
+			}
+
+			this.value.push({});
+		},
+		deleteField: function(index)
+		{
+			this.value.splice(index,1);
+		},
+	},
+})
+
 Vue.component('tab-meta', {
 Vue.component('tab-meta', {
 	props: ['saved', 'errors', 'formdata', 'schema'],
 	props: ['saved', 'errors', 'formdata', 'schema'],
 	template: '<section><form>' +
 	template: '<section><form>' +
@@ -473,9 +560,18 @@ let meta = new Vue({
             }
             }
         });
         });
 
 
+        /* 	update single value or array 
+			this.$set(this.someObject, 'b', 2)  */
 		FormBus.$on('forminput', formdata => {
 		FormBus.$on('forminput', formdata => {
 			this.$set(this.formData[this.currentTab], formdata.name, formdata.value);
 			this.$set(this.formData[this.currentTab], formdata.name, formdata.value);
 		});
 		});
+
+		/*  update values that are objects 
+			this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 }) */
+
+		FormBus.$on('forminputobject', formdata => {
+			this.formData[this.currentTab][formdata.name] = Object.assign({}, this.formData[this.currentTab][formdata.name], formdata.value);			
+		});
 	},
 	},
 	methods: {
 	methods: {
 		saveForm: function()
 		saveForm: function()