Browse Source

Version 1.2.9: Add videos and frontend forms

Sebastian 6 years ago
parent
commit
08ece40117
52 changed files with 989 additions and 441 deletions
  1. 3 0
      .gitignore
  2. 1 1
      cache/lastCache.txt
  3. 0 1
      content/00-Welcome/03-test.txt
  4. 0 16
      content/01-stellenanzeigen/00-job-einstellen.md
  5. 0 4
      content/01-stellenanzeigen/index.md
  6. 129 31
      plugins/contactform/contactform.php
  7. 36 38
      plugins/contactform/contactform.yaml
  8. 0 26
      plugins/contactform/css/textadds.css
  9. 0 40
      plugins/contactform/templates/contactform.twig
  10. 70 2
      system/Controllers/ContentApiController.php
  11. 106 0
      system/Controllers/FormController.php
  12. 3 1
      system/Controllers/PageController.php
  13. 3 3
      system/Controllers/SettingsController.php
  14. 1 1
      system/Extensions/TwigMarkdownExtension.php
  15. 24 16
      system/Models/Field.php
  16. 13 12
      system/Models/Fields.php
  17. 3 3
      system/Models/ProcessImage.php
  18. 8 2
      system/Models/Validation.php
  19. 53 7
      system/Plugin.php
  20. 3 1
      system/Routes/Api.php
  21. 3 0
      system/Routes/Web.php
  22. 0 1
      system/Settings.php
  23. 9 9
      system/author/css/fontello/LICENSE.txt
  24. 29 11
      system/author/css/fontello/config.json
  25. 9 6
      system/author/css/fontello/css/fontello-codes.css
  26. 3 3
      system/author/css/fontello/css/fontello-embedded.css
  27. 9 6
      system/author/css/fontello/css/fontello-ie7-codes.css
  28. 9 6
      system/author/css/fontello/css/fontello-ie7.css
  29. 16 13
      system/author/css/fontello/css/fontello.css
  30. 17 12
      system/author/css/fontello/demo.html
  31. BIN
      system/author/css/fontello/font/fontello.eot
  32. 13 7
      system/author/css/fontello/font/fontello.svg
  33. BIN
      system/author/css/fontello/font/fontello.ttf
  34. BIN
      system/author/css/fontello/font/fontello.woff
  35. BIN
      system/author/css/fontello/font/fontello.woff2
  36. 41 3
      system/author/css/style.css
  37. 1 0
      system/author/editor/editor-blox.twig
  38. 34 0
      system/author/js/lazy-video.js
  39. 114 15
      system/author/js/vue-blox.js
  40. 10 9
      system/author/layouts/layoutBlox.twig
  41. 1 1
      system/author/partials/editorNavi.twig
  42. 73 0
      system/author/partials/fields.twig
  43. 32 0
      system/author/partials/form.twig
  44. 0 66
      system/author/partials/forms.twig
  45. 1 1
      system/author/settings/plugins.twig
  46. 3 5
      system/author/settings/themes.twig
  47. 6 2
      themes/typemill/chapter.twig
  48. 40 3
      themes/typemill/css/style.css
  49. 55 0
      themes/typemill/js/script.js
  50. 4 55
      themes/typemill/partials/layout.twig
  51. 1 2
      themes/typemill/typemill.yaml
  52. BIN
      typemill-1.2.8-f.zip

+ 3 - 0
.gitignore

@@ -5,9 +5,12 @@ plugins/demo
 plugins/disqus
 plugins/disqus
 plugins/download
 plugins/download
 plugins/finalwords
 plugins/finalwords
+plugins/joblistings
+plugins/mail
 plugins/textadds
 plugins/textadds
 plugins/version
 plugins/version
 settings/settings.yaml
 settings/settings.yaml
+settings/formdata.yaml
 settings/users
 settings/users
 system/vendor
 system/vendor
 tests
 tests

+ 1 - 1
cache/lastCache.txt

@@ -1 +1 @@
-1544479161
+1546546314

+ 0 - 1
content/00-Welcome/03-test.txt

@@ -1 +0,0 @@
-["# Add Title","Add Content"]

+ 0 - 16
content/01-stellenanzeigen/00-job-einstellen.md

@@ -1,16 +0,0 @@
-# Stellenangebot veröffentlichen
-
-CMSstash erreicht pro Monat mehrere tausend CMS-Experten in ganz Deutschland. Detaillierte Angaben finden sie in den [Mediadaten](/mediadaten). Unternehmen können diese Reichweite nutzen und ihre Stellenanzeigen mit CMS-Bezug auf CMSstash veröffentlichen. Wir bieten folgende Konditionen an:
-
-| Kondition | Anzeige Standard | Anzeige Pro |
-|-----------|-----------|----------|
-| Laufzeit | 8 Wochen | 8 Wochen |
-| Exklusiver Teaser auf allen Seiten | Nein | 4 Wochen |
-| Tweets | 1 Tweet | 3 Tweets |
-| __Preis__  | **59,- Euro** | **159,- Euro** |
-| _(keine Ausweisung der Mwst. nach § 19 UStG)_ |  |  |
-
-## Stellenanzeige aufgeben
-
-Bei Interesse schreiben Sie uns gerne über [jobs@cmsstash.de](mailto:jobs@cmsstash.de) an oder nutzen Sie das Kontaktformular. Aufgrund der inhaltlichen Ausrichtung können wir nur Stellenangebote mit einem klaren Bezug zum Thema Content Management Systeme veröffentlichen.
-

+ 0 - 4
content/01-stellenanzeigen/index.md

@@ -1,4 +0,0 @@
-# Add Title
-
-Add Content
-

+ 129 - 31
plugins/contactform/contactform.php

@@ -6,70 +6,168 @@ use \Typemill\Plugin;
 
 
 class ContactForm extends Plugin
 class ContactForm extends Plugin
 {
 {
-	protected $item;
-	protected $originalHtml;
+	protected $settings;
 	protected $pluginSettings;
 	protected $pluginSettings;
+	protected $originalHtml;
 	
 	
     public static function getSubscribedEvents()
     public static function getSubscribedEvents()
     {
     {
 		return array(
 		return array(
 			'onSessionSegmentsLoaded' 	=> 'onSessionSegmentsLoaded',
 			'onSessionSegmentsLoaded' 	=> 'onSessionSegmentsLoaded',
-			'onOriginalLoaded' 			=> 'onOriginalLoaded',
 			'onHtmlLoaded' 				=> 'onHtmlLoaded',
 			'onHtmlLoaded' 				=> 'onHtmlLoaded',
 		);
 		);
     }
     }
-
-	# add the path for session and csrf-protection
+	
+	# add the path stored in user-settings to initiate session and csrf-protection
 	public function onSessionSegmentsLoaded($segments)
 	public function onSessionSegmentsLoaded($segments)
 	{
 	{
 		$this->pluginSettings = $this->getPluginSettings('contactform');
 		$this->pluginSettings = $this->getPluginSettings('contactform');
 		
 		
-		if($this->getPath() == $this->pluginSettings['page'])
+		if($this->getPath() == $this->pluginSettings['page_value'])
 		{
 		{
-			$data = $segments->getData();
-			$data[] = $this->pluginSettings['page'];
+			$data 	= $segments->getData();
+			$data[]	= $this->pluginSettings['page_value'];
+			
 			$segments->setData($data);
 			$segments->setData($data);
 		}
 		}
 	}
 	}
+
+	# create the output
+	public function onHtmlLoaded($html)
+	{
+		if($this->getPath() == $this->pluginSettings['page_value'])
+		{
+			$content = $html->getData();
+			
+			# add css 
+			$this->addCSS('/contactform/css/contactform.css');
+			
+			# check if form data have been stored
+			$formdata = $this->getFormdata('contactform');
+
+			if($formdata)
+			{
+				# process the form-data
+				$sendmail = $this->sendMail($formdata);
+				if($sendmail)
+				{
+					$mailresult = '<div class="mailresult">' . $this->markdownToHtml($this->pluginSettings['message_success']) . '</div>';
+				}
+				else
+				{
+					$mailresult = '<div class="mailresult">' . $this->markdownToHtml($this->pluginSettings['message_error']) . '</div>';						
+				}
 	
 	
-	# get the original html without manipulations
-	public function onOriginalLoaded($original)
+				# add thank you to the content
+				$content = $content . $mailresult;
+			}
+			else
+			{
+				# get the public forms for the plugin
+				$contactform = $this->generateForm('contactform');				
+				
+				# add forms to the content
+				$content = $content . $contactform;					
+			}
+			$html->setData($content);
+		}
+	}
+
+	private function sendMail($formdata)
+	{		
+		$mailto 		= $this->pluginSettings['mailto'];
+		$subject 		= $formdata['subject'];
+		$message 		= wordwrap($formdata['message'],70);
+		$header 		= 'From: ' . $formdata['email'] . "\r\n" .
+						  'Reply-To: ' . $formdata['email'] . "\r\n" .
+						  'X-Mailer: PHP/' . phpversion();
+
+		return @mail($mailto, $subject, $message, $header);
+	}
+	
+	# check if the mail-plugin is active
+	/*
+	public function onTwigLoaded()
 	{
 	{
-		if(substr($this->getPath(), 0, strlen($this->pluginSettings['area'])) === $this->pluginSettings['area'])
+		$this->settings = $this->getSettings();
+		
+		if(isset($this->settings['plugins']['mail']) OR !$this->settings['plugins']['mail']['active'])
+		{
+			$this->container->flash->addMessage('error', 'You have to activate the mail-plugin to use the contactform.');
+		}
+	}
+	*/
+	
+	# get the original html without manipulations from other plugins
+	/*
+	public function onOriginalLoaded($original)
+	{				
+		if(substr($this->getPath(), 0, strlen($this->pluginSettings['area_value'])) === $this->pluginSettings['area_value'])
 		{
 		{
 			$this->originalHtml = $original->getHTML($urlrel = false);
 			$this->originalHtml = $original->getHTML($urlrel = false);
 		}
 		}
 	}
 	}
+	*/
 	
 	
+	# create the output
+	/*
 	public function onHtmlLoaded($html)
 	public function onHtmlLoaded($html)
-	{		
-		if(substr($this->getPath(), 0, strlen($this->pluginSettings['area'])) === $this->pluginSettings['area'])
+	{
+		if(substr($this->getPath(), 0, strlen($this->pluginSettings['area_value'])) === $this->pluginSettings['area_value'])
 		{
 		{
 			$content = $this->originalHtml;
 			$content = $this->originalHtml;
 			
 			
-			if($this->getPath() == $this->pluginSettings['page'])
-			{
-				
-				
-				$this->generateForm('contactform');
-				
-				
+			if($this->getPath() == $this->pluginSettings['page_value'])
+			{				
 				# add css 
 				# add css 
-				# $this->addCSS('/textadds/css/textadds.css');
-
-				# get Twig Instance and add the cookieconsent template-folder to the path
-				$twig 	= $this->getTwig();
-				$loader = $twig->getLoader();
-				$loader->addPath(__DIR__ . '/templates');
-
-				# fetch the template and render it with twig
-				$contactform = $twig->fetch('/contactform.twig', $this->pluginSettings);
+				$this->addCSS('/contactform/css/contactform.css');
+			
+				# check if form data have been stored
+				$formdata = $this->getFormdata('contactform');
+				
+				if($formdata)
+				{
+					# process the form-data
+					$sendmail = $this->sendMail($formdata);
+					if($sendmail)
+					{
+						$mailresult = '<div class="mailresult"><h2>Thank you!</h2><p>Your Message has been send.</p></div>';
+					}
+					else
+					{
+						$mailresult = '<div class="mailresult"><h2>Error</h2><p>We could not send your message. Please send the mail manually.</p></div>';						
+					}
+					/*
+					print_r($formdata);
+					die();
+					$mail = $this->container->mail->send('mail/welcome.twig', ['user' => $user, 'url' => $host], function($message) use ($user){
+						  $message->to($user['email']);
+						  $message->subject('Dein Account bei der Regierungsmannschaft');
+					});
+					
+					if(!$mail)
+					{
+						$this->container->flash->addMessage('error', 'Die Bestätigungsmail konnte nicht verschickt werden.');
+					}
+					
+					# create thank you page
+					
+					# add thank you to the content
+					$content = $this->originalHtml . $mailresult;
+				}
+				else
+				{
+					# get the public forms for the plugin
+					$contactform = $this->generateForm('contactform');				
 				
 				
-				$content = $this->originalHtml . $contactform;
+					# add forms to the content
+					$content = $this->originalHtml . $contactform;					
+				}
 			}
 			}
 			
 			
 			$html->setData($content);
 			$html->setData($content);
-			$html->stopPropagation();			
+			$html->stopPropagation();
 		}
 		}
 	}
 	}
+	*/
 }
 }

+ 36 - 38
plugins/contactform/contactform.yaml

@@ -6,91 +6,89 @@ homepage: https://typemill.net
 licence: MIT
 licence: MIT
 
 
 settings:
 settings:
-  page: ''
-  name: 'Name: '
-  email: 'E-Mail: '
-  subject: 'Subject: '
-  message: 'Message: '
-  button: 'Send Message'
+  message_success: "## Thank you!\r\n\r\nWe got your message and will answer as soon as possible."
+  message_error: "## Error\r\n\r\nWe could not send your message. Please send your e-mail manually."
 
 
 forms:
 forms:
   fields:
   fields:
 
 
-    page:
-      type: text
-      label: Path to the page where to show the form
-      placeholder: 'path/to/page'
+    mailto:
+      type: email
+      label: Your e-mail-address
       required: true
       required: true
 
 
-    area:
+    page_value:
       type: text
       type: text
-      label: Path to the area with original content
-      placeholder: 'path/to/rootpage'
+      label: Path to page where to include the form
+      placeholder: 'path/to/page'
       required: true
       required: true
 
 
-    name:
+    name_label:
       type: text
       type: text
       label: Label for Name Input Field
       label: Label for Name Input Field
-      placeholder: 'Name: '
       required: true
       required: true
 
 
-    email:
+    email_label:
       type: text
       type: text
       label: Label for E-Mail-Field
       label: Label for E-Mail-Field
-      placeholder: 'E-Mail: '
       required: true
       required: true
 
 
-    subject:
+    subject_label:
       type: text
       type: text
       label: Label for Subject-Field
       label: Label for Subject-Field
-      placeholder: 'Subject: '
       required: true
       required: true
 
 
-    message:
+    message_label:
       type: text
       type: text
       label: Label for Message
       label: Label for Message
-      placeholder: 'Message: '
       required: true
       required: true
 
 
-    button:
+    button_label:
       type: text
       type: text
       label: Label for Button
       label: Label for Button
       placeholder: 'Send Message'
       placeholder: 'Send Message'
       required: true
       required: true
 
 
-    hint:
+    legalnotice:
       type: textarea
       type: textarea
+      rows: 5;
       label: Text below button (use markdown)
       label: Text below button (use markdown)
       placeholder: 'Add your legal text or other hints here'
       placeholder: 'Add your legal text or other hints here'
 
 
-frontend:
+    message_success:
+      type: textarea
+      rows: 5;
+      label: Message if mail is send (use markdown)
+      placeholder: 'Add your legal text or other hints here'
+
+    message_error:
+      type: textarea
+      rows: 5;
+      label: Message if mail failed (use markdown)
+      placeholder: 'Add your legal text or other hints here'
+
+public:
   fields:
   fields:
 
 
     name:
     name:
       type: text
       type: text
-      label: Label for Name Input Field
-      placeholder: 'Name: '
+      label: name_label
       required: true
       required: true
 
 
     email:
     email:
-      type: text
-      label: Label for E-Mail-Field
-      placeholder: 'E-Mail: '
+      type: email
+      label: email_label
       required: true
       required: true
 
 
     subject:
     subject:
       type: text
       type: text
-      label: Label for Subject-Field
-      placeholder: 'Subject: '
+      label: subject_label
       required: true
       required: true
 
 
     message:
     message:
-      type: text
-      label: Label for Message
-      placeholder: 'Message: '
+      type: textarea
+      label: message_label
       required: true
       required: true
 
 
-    hint:
-      type: textarea
-      label: Text below button (use markdown)
-      placeholder: 'Add your legal text or other hints here'
+    legalnotice:
+      type: paragraph

+ 0 - 26
plugins/contactform/css/textadds.css

@@ -1,26 +0,0 @@
-.contentadd{
-	display: block;
-	position: relative;
-	width: 100%;
-	border-top: 1px solid #ccc;
-	border-bottom: 1px solid #ccc;
-}
-.contentadd span{
-	position: absolute;
-	right: 10px;
-	top: 20px;
-	text-transform: uppercase;
-	color: #ccc;
-	font-size: 0.6em;
-}
-.contentadd h3{
-	margin-top: 20px;
-	margin-bottom: 0px;
-}
-.contentadd small{
-	margin: 0px;
-}
-.contentadd p{
-	margin-top: 0px;
-	margin-bottom: 20px;
-}

+ 0 - 40
plugins/contactform/templates/contactform.twig

@@ -1,40 +0,0 @@
-<form method="POST" action="{{ path_for('settings.save') }}">
-
-	<fieldset>
-
-		<div class="medium{{ errors.contact.name ? ' error' : '' }}">
-			<label for="contact[name]">{{ name }} *</label>
-			<input type="text" name="contact[name]" id="name" pattern=".{2,20}" required title="Use 2 to 20 characters." value="{{ old.contact.title ? old.contact.name : contact.name }}" />
-			{% if errors.contact.title %}
-				<span class="error">{{ errors.contact.name | first }}</span>
-			{% endif %}
-		</div>
-		<div class="medium{{ errors.contact.email ? ' error' : '' }}">
-			<label for="contact[email]">{{ email }} *</label>
-			<input type="text" name="contact[email]" id="email" pattern=".{2,20}" required title="Use 2 to 20 characters." value="{{ old.contact.email ? old.contact.email : contact.email }}" />
-			{% if errors.contact.title %}
-				<span class="error">{{ errors.contact.email | first }}</span>
-			{% endif %}
-		</div>
-		<div class="medium{{ errors.contact.subject ? ' error' : '' }}">
-			<label for="contact[email]">{{ subject }} *</label>
-			<input type="text" name="contact[subject]" id="subject" pattern=".{2,20}" required title="Use 2 to 20 characters." value="{{ old.contact.subject ? old.contact.subject : contact.subject }}" />
-			{% if errors.contact.title %}
-				<span class="error">{{ errors.contact.email | first }}</span>
-			{% endif %}
-		</div>
-		<div class="medium{{ errors.contact.message ? ' error' : '' }}">
-			<label for="contact[email]">{{ message }} *</label>
-			<input type="textfield" name="contact[message]" id="message" pattern=".{2,20}" required title="Use 2 to 20 characters." value="{{ old.contact.message ? old.contact.message : contact.message }}" />
-			{% if errors.contact.title %}
-				<span class="error">{{ errors.contact.message | first }}</span>
-			{% endif %}
-		</div>
-		<small>{{ hint }}</small>
-		
-	</fieldset>
-	
-	<input type="submit" value="{{ button }}" />
-	{{ csrf_field() | raw }}	
-
-</form>

+ 70 - 2
system/Controllers/ContentApiController.php

@@ -856,7 +856,7 @@ class ContentApiController extends ContentController
 		
 		
 		$imageProcessor	= new ProcessImage();
 		$imageProcessor	= new ProcessImage();
 		
 		
-		if($imageProcessor->createImage($this->params['image'], $this->settings['images'], $name = false))
+		if($imageProcessor->createImage($this->params['image'], $this->settings['images']))
 		{
 		{
 			return $response->withJson(array('errors' => false));		
 			return $response->withJson(array('errors' => false));		
 		}
 		}
@@ -867,7 +867,7 @@ class ContentApiController extends ContentController
 	public function publishImage(Request $request, Response $response, $args)
 	public function publishImage(Request $request, Response $response, $args)
 	{
 	{
 		$params 		= $request->getParsedBody();
 		$params 		= $request->getParsedBody();
-						
+
 		$imageProcessor	= new ProcessImage();
 		$imageProcessor	= new ProcessImage();
 		
 		
 		$imageUrl 		= $imageProcessor->publishImage($this->settings['images'], $name = false);
 		$imageUrl 		= $imageProcessor->publishImage($this->settings['images'], $name = false);
@@ -882,4 +882,72 @@ class ContentApiController extends ContentController
 
 
 		return $response->withJson(array('errors' => 'could not store image to temporary folder'));	
 		return $response->withJson(array('errors' => 'could not store image to temporary folder'));	
 	}
 	}
+
+	public function saveVideoImage(Request $request, Response $response, $args)
+	{
+		/* get params from call */
+		$this->params 	= $request->getParams();
+		$this->uri 		= $request->getUri();
+		$class			= false;
+
+		$imageUrl		= $this->params['markdown'];
+		
+		if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
+		{
+			$videoID 	= str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
+			$videoID 	= strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
+			$class		= 'youtube';
+		}
+		if(strpos($imageUrl, 'https://youtu.be/') !== false)
+		{
+			$videoID 	= str_replace('https://youtu.be/', '', $imageUrl);
+			$videoID	= strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
+			$class		= 'youtube';
+		}
+		
+		if($class == 'youtube')
+		{
+			$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
+			$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
+		}
+
+		$ctx = stream_context_create(array(
+			'https' => array(
+				'timeout' => 1
+				)
+			)
+		);
+		
+		$imageData		= @file_get_contents($videoURLmaxres, 0, $ctx);
+		if($imageData === false)
+		{
+			$imageData	= @file_get_contents($videoURL0, 0, $ctx);
+			if($imageData === false)
+			{
+				return $response->withJson(array('errors' => 'could not get the video image'));
+			}
+		}
+		
+		$imageData64	= 'data:image/jpeg;base64,' . base64_encode($imageData);
+		$desiredSizes	= ['live' => ['width' => 560, 'height' => 315]];
+		$imageProcessor	= new ProcessImage();
+		$tmpImage		= $imageProcessor->createImage($imageData64, $desiredSizes);
+		
+		if(!$tmpImage)
+		{
+			return $response->withJson(array('errors' => 'could not create temporary image'));			
+		}
+		
+		$imageUrl 		= $imageProcessor->publishImage($desiredSizes, $videoID);
+		if($imageUrl)
+		{
+			$this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}';
+
+			$request 	= $request->withParsedBody($this->params);
+			
+			return $this->updateBlock($request, $response, $args);
+		}
+
+		return $response->withJson(array('errors' => 'could not store the preview image'));	
+	}
 }
 }

+ 106 - 0
system/Controllers/FormController.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace Typemill\Controllers;
+
+use Typemill\Models\Validation;
+use Typemill\Models\WriteYaml;
+
+class FormController extends Controller
+{
+	/*************************************
+	**	SAVE THEME- AND PLUGIN-SETTINGS	**
+	*************************************/
+
+	public function savePublicForm($request, $response, $args)
+	{
+		if($request->isPost())
+		{	
+			$params 			= $request->getParams();
+			reset($params);
+			$pluginName 		= key($params);
+			$referer			= $request->getHeader('HTTP_REFERER');
+			
+			# simple bot check with honeypot
+			if(isset($params[$pluginName]['personal-mail']))
+			{
+				if($params[$pluginName]['personal-mail'] != '')
+				{
+					$this->c->flash->addMessage('publicform', 'bot');
+					return $response->withRedirect($referer[0]);			
+				}
+				unset($params[$pluginName]['personal-mail']);
+			}
+
+			if(isset($params[$pluginName]))
+			{
+				# validate the user-input
+				$this->validateInput('plugins', $pluginName, $params[$pluginName]);
+			}
+			
+			# check for errors and redirect to path, if errors found
+			if(isset($_SESSION['errors']))
+			{
+				$this->c->flash->addMessage('error', 'Please correct the errors');
+				return $response->withRedirect($referer[0]);
+			}
+			
+			# clean up and make sure that only validated data are stored
+			$data = [ $pluginName => $params[$pluginName]];
+			
+			# create write object
+			$writeYaml = new WriteYaml();
+			
+			# write the form data into yaml file
+			$writeYaml->updateYaml('settings', 'formdata.yaml', $data);
+			
+			# add message and return to original site
+			$this->c->flash->addMessage('formdata', $pluginName);
+			return $response->withRedirect($referer[0]);
+		}
+	}
+
+	private function validateInput($objectType, $objectName, $userInput)
+	{
+		# get settings and start validation
+		$originalSettings 	= \Typemill\Settings::getObjectSettings($objectType, $objectName);
+		$userSettings 		= \Typemill\Settings::getUserSettings();
+		$validate			= new Validation();
+
+		if(isset($originalSettings['public']['fields']))
+		{
+			/* flaten the multi-dimensional array with fieldsets to a one-dimensional array */
+			$originalFields = array();
+			foreach($originalSettings['public']['fields'] as $fieldName => $fieldValue)
+			{
+				if(isset($fieldValue['fields']))
+				{
+					foreach($fieldValue['fields'] as $subFieldName => $subFieldValue)
+					{
+						$originalFields[$subFieldName] = $subFieldValue;
+					}
+				}
+				else
+				{
+					$originalFields[$fieldName] = $fieldValue;
+				}
+			}
+
+			/* take the user input data and iterate over all fields and values */
+			foreach($userInput as $fieldName => $fieldValue)
+			{
+				/* get the corresponding field definition from original plugin settings */
+				$fieldDefinition = isset($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
+
+				if($fieldDefinition)
+				{
+					/* validate user input for this field */
+					$validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition);
+				}
+				if(!$fieldDefinition && $fieldName != 'active')
+				{
+					$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
+				}
+			}
+		}
+	}
+}

+ 3 - 1
system/Controllers/PageController.php

@@ -137,8 +137,10 @@ class PageController extends Controller
 		/* get the first image from content array */
 		/* get the first image from content array */
 		$firstImage		= $this->getFirstImage($contentArray);
 		$firstImage		= $this->getFirstImage($contentArray);
 		
 		
+		$itemUrl 		= isset($item->urlRel) ? $item->urlRel : false;
+		
 		/* parse markdown-content-array to content-string */
 		/* parse markdown-content-array to content-string */
-		$contentHTML	= $parsedown->markup($contentArray, $item->urlRel);
+		$contentHTML	= $parsedown->markup($contentArray, $itemUrl);
 		$contentHTML 	= $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
 		$contentHTML 	= $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
 		
 		
 		/* extract the h1 headline*/
 		/* extract the h1 headline*/

+ 3 - 3
system/Controllers/SettingsController.php

@@ -121,7 +121,7 @@ class SettingsController extends Controller
 		$user		= new User();
 		$user		= new User();
 		$users		= $user->getUsers();
 		$users		= $user->getUsers();
 		$route 		= $request->getAttribute('route');
 		$route 		= $request->getAttribute('route');
-	
+		
 		$this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'users' => $users, 'route' => $route->getName() ));
 		$this->render($response, 'settings/themes.twig', array('settings' => $userSettings, 'themes' => $themedata, 'users' => $users, 'route' => $route->getName() ));
 	}
 	}
 	
 	
@@ -241,7 +241,7 @@ class SettingsController extends Controller
 			$pluginSettings	= array();
 			$pluginSettings	= array();
 			$userInput 		= $request->getParams();
 			$userInput 		= $request->getParams();
 			$validate		= new Validation();
 			$validate		= new Validation();
-
+			
 			/* use the stored user settings and iterate over all original plugin settings, so we do not forget any... */
 			/* use the stored user settings and iterate over all original plugin settings, so we do not forget any... */
 			foreach($userSettings['plugins'] as $pluginName => $pluginUserSettings)
 			foreach($userSettings['plugins'] as $pluginName => $pluginUserSettings)
 			{
 			{
@@ -323,7 +323,7 @@ class SettingsController extends Controller
 				if(!$fieldDefinition && $fieldName != 'active')
 				if(!$fieldDefinition && $fieldName != 'active')
 				{
 				{
 					$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
 					$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
-				}				
+				}
 			}
 			}
 		}
 		}
 	}
 	}

+ 1 - 1
system/Extensions/TwigMarkdownExtension.php

@@ -19,6 +19,6 @@ class TwigMarkdownExtension extends \Twig_Extension
 		
 		
 		$markdownArray = $parsedown->text($markdown);
 		$markdownArray = $parsedown->text($markdown);
 		
 		
-		return $parsedown->markup($markdownArray);
+		return $parsedown->markup($markdownArray, false);
 	}
 	}
 }
 }

+ 24 - 16
system/Models/Field.php

@@ -44,7 +44,8 @@ class Field
 									'url',
 									'url',
 									'week',
 									'week',
 									'textarea',
 									'textarea',
-									'select'
+									'select',
+									'paragraph'
 								);
 								);
 								
 								
 	/* defines all boolean attributes, that are allowed for fields */
 	/* defines all boolean attributes, that are allowed for fields */
@@ -80,20 +81,17 @@ class Field
 	{
 	{
 		$this->setName($fieldName);
 		$this->setName($fieldName);
 		
 		
-		if(isset($fieldConfigs['type']))
-		{
-			$this->setType($fieldConfigs['type']);
-		}
+		$type = isset($fieldConfigs['type']) ? $fieldConfigs['type'] : false;
+		$this->setType($type);
+
+		$label = isset($fieldConfigs['label']) ? $fieldConfigs['label'] : false;
+		$this->setLabel($label);
+
+		$checkboxlabel = isset($fieldConfigs['checkboxlabel']) ? $fieldConfigs['checkboxlabel'] : false;
+		$this->setCheckboxLabel($checkboxlabel);
 		
 		
-		if(isset($fieldConfigs['label']))
-		{
-			$this->setLabel($fieldConfigs['label']);
-		}
-				
-		if(isset($fieldConfigs['options']))
-		{
-			$this->setOptions($fieldConfigs['options']);
-		}
+		$options = isset($fieldConfigs['options']) ? $fieldConfigs['options'] : array();
+		$this->setOptions($options);
 		
 		
 		$this->setAttributes($fieldConfigs);
 		$this->setAttributes($fieldConfigs);
 		
 		
@@ -125,7 +123,7 @@ class Field
 		return $this->type;
 		return $this->type;
 	}
 	}
 	
 	
-	private function setLabel($label)
+	public function setLabel($label)
 	{
 	{
 		$this->label = $label;
 		$this->label = $label;
 	}
 	}
@@ -134,6 +132,16 @@ class Field
 	{
 	{
 		return $this->label;
 		return $this->label;
 	}
 	}
+
+	public function setCheckboxLabel($label)
+	{
+		$this->checkboxLabel = $label;
+	}
+
+	public function getCheckboxLabel()
+	{
+		return $this->checkboxLabel;
+	}
 	
 	
 	public function setContent($content)
 	public function setContent($content)
 	{
 	{
@@ -177,7 +185,7 @@ class Field
 	public function getAttributes()
 	public function getAttributes()
 	{
 	{
 		$string = false;
 		$string = false;
-		
+				
 		foreach($this->attributes as $key => $attribute)
 		foreach($this->attributes as $key => $attribute)
 		{
 		{
 			$string .= ' ' . $key;
 			$string .= ' ' . $key;

+ 13 - 12
system/Models/Fields.php

@@ -7,13 +7,13 @@ use Typemill\Models\Field;
 class Fields
 class Fields
 {
 {
 	public function getFields($userSettings, $objectType, $objectName, $objectSettings, $formType = false)
 	public function getFields($userSettings, $objectType, $objectName, $objectSettings, $formType = false)
-	{			
+	{
 		# hold all fields in array 
 		# hold all fields in array 
 		$fields = array();
 		$fields = array();
 
 
-		# formtype are backendforms or frontendforms, only relevant for plugins for now
+		# formtype are backend forms or public forms, only relevant for plugins for now
 		$formType = $formType ? $formType : 'forms';
 		$formType = $formType ? $formType : 'forms';
-		
+
 		# iterate through all fields of the objectSetting (theme or plugin)
 		# iterate through all fields of the objectSetting (theme or plugin)
 		foreach($objectSettings[$formType]['fields'] as $fieldName => $fieldConfigurations)
 		foreach($objectSettings[$formType]['fields'] as $fieldName => $fieldConfigurations)
 		{
 		{
@@ -56,7 +56,7 @@ class Fields
 				}
 				}
 			
 			
 				# Now prepopulate the field object with the value */
 				# Now prepopulate the field object with the value */
-				if($field->getType() == "textarea")
+				if($field->getType() == "textarea" || $field->getType() == "paragraph")
 				{
 				{
 					if($userValue)
 					if($userValue)
 					{
 					{
@@ -66,25 +66,26 @@ class Fields
 				elseif($field->getType() == "checkbox")
 				elseif($field->getType() == "checkbox")
 				{
 				{
 					# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
 					# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
-					if(isset($userSettings[$objectType][$objectName][$fieldName]))
-					{
-						$field->setAttribute('checked', 'checked');
-					}
-					else
+					if(!isset($userSettings[$objectType][$objectName][$fieldName]))
 					{
 					{
-						$field->unsetAttribute('checked');
+						$field->unsetAttribute('checked');						
 					}
 					}
 				}
 				}
 				else
 				else
 				{
 				{
 					$field->setAttributeValue('value', $userValue);	
 					$field->setAttributeValue('value', $userValue);	
 				}
 				}
-
+				
+				if(isset($fieldConfigurations['label']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['label']]))
+				{
+					$field->setLabel($userSettings[$objectType][$objectName][$fieldConfigurations['label']]);
+				}
+				
 				# add the field to the field-List
 				# add the field to the field-List
 				$fields[] = $field;
 				$fields[] = $field;
 
 
 			}
 			}
-		}	
+		}
 		return $fields;
 		return $fields;
 	}
 	}
 }
 }

+ 3 - 3
system/Models/ProcessImage.php

@@ -3,7 +3,7 @@ namespace Typemill\Models;
 
 
 class ProcessImage
 class ProcessImage
 {
 {
-	public function createImage(string $image, array $desiredSizes, $name)
+	public function createImage(string $image, array $desiredSizes)
 	{
 	{
 		# fix error from jpeg-library
 		# fix error from jpeg-library
 		ini_set ('gd.jpeg_ignore_warning', 1);
 		ini_set ('gd.jpeg_ignore_warning', 1);
@@ -60,7 +60,7 @@ class ProcessImage
 		return false;
 		return false;
 	}
 	}
 	
 	
-	public function publishImage(array $desiredSizes)
+	public function publishImage(array $desiredSizes, $name = false)
 	{
 	{
 		/* get images from tmp folder */
 		/* get images from tmp folder */
 		$basePath		= getcwd() . DIRECTORY_SEPARATOR . 'media';
 		$basePath		= getcwd() . DIRECTORY_SEPARATOR . 'media';
@@ -71,7 +71,7 @@ class ProcessImage
 		if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
 		if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
 		if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
 		if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
 
 
-		$name 			= uniqid();
+		$name 			= $name ? $name : uniqid();
 		
 		
 		$files 			= scandir($tmpFolder);
 		$files 			= scandir($tmpFolder);
 		$success		= true;
 		$success		= true;

+ 8 - 2
system/Models/Validation.php

@@ -312,7 +312,6 @@ class Validation
 	{	
 	{	
 		$v = new Validator(array($fieldName => $fieldValue));
 		$v = new Validator(array($fieldName => $fieldValue));
 
 
-		
 		if(isset($fieldDefinitions['required']))
 		if(isset($fieldDefinitions['required']))
 		{
 		{
 			$v->rule('required', $fieldName);
 			$v->rule('required', $fieldName);
@@ -364,11 +363,18 @@ class Validation
 				$v->rule('noHTML', $fieldName);
 				$v->rule('noHTML', $fieldName);
 				// $v->rule('regex', $fieldName, '/<[^<]+>/');
 				// $v->rule('regex', $fieldName, '/<[^<]+>/');
 				break;
 				break;
+			case "paragraph":
+				$v->rule('lengthMax', $fieldName, 1000);
+				$v->rule('noHTML', $fieldName);
+				break;
+			case "password":
+				$v->rule('lengthMax', $fieldName, 100);
+				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');				
 		}
 		}
-		
+
 		return $this->validationResult($v, $objectName);
 		return $this->validationResult($v, $objectName);
 	}
 	}
 	
 	

+ 53 - 7
system/Plugin.php

@@ -4,6 +4,8 @@ namespace Typemill;
 
 
 use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Typemill\Models\Fields;
 use Typemill\Models\Fields;
+use Typemill\Models\WriteYaml;
+use Typemill\Extensions\ParsedownExtension;
 
 
 abstract class Plugin implements EventSubscriberInterface
 abstract class Plugin implements EventSubscriberInterface
 {	
 {	
@@ -65,7 +67,7 @@ abstract class Plugin implements EventSubscriberInterface
 		$function = new \Twig_SimpleFunction($name, $function);
 		$function = new \Twig_SimpleFunction($name, $function);
 		$this->container->view->getEnvironment()->addFunction($function);
 		$this->container->view->getEnvironment()->addFunction($function);
 	}
 	}
-	
+		
 	protected function addJS($JS)
 	protected function addJS($JS)
 	{
 	{
 		$this->container->assets->addJS($JS);
 		$this->container->assets->addJS($JS);
@@ -86,19 +88,63 @@ abstract class Plugin implements EventSubscriberInterface
 		$this->container->assets->addInlineCSS($CSS);		
 		$this->container->assets->addInlineCSS($CSS);		
 	}
 	}
 	
 	
+	protected function markdownToHtml($markdown)
+	{
+		$parsedown 		= new ParsedownExtension();
+		
+		$contentArray 	= $parsedown->text($markdown);
+		$html			= $parsedown->markup($contentArray,false);
+		
+		return $html;
+	}
+	
+	protected function getFormData($pluginName)
+	{
+		$flash = $this->container->flash->getMessages();
+		if(isset($flash['formdata']))
+		{
+			$yaml 		= new Models\WriteYaml();
+			$formdata 	= $yaml->getYaml('settings', 'formdata.yaml');
+			$yaml->updateYaml('settings', 'formdata.yaml', '');
+			
+			if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName]))
+			{
+				return $formdata[$pluginName];
+			}
+		}
+		return false;
+	}
+	
 	protected function generateForm($pluginName)
 	protected function generateForm($pluginName)
 	{
 	{
 		$fieldsModel = new Fields();
 		$fieldsModel = new Fields();
 		
 		
-		$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
-				
-		if(isset($pluginDefinitions['frontend']['fields']))
+		$pluginDefinitions 	= \Typemill\Settings::getObjectSettings('plugins', $pluginName);
+		$settings 			= $this->getSettings();
+		$buttonlabel		= isset($settings['plugins'][$pluginName]['button_label']) ? $settings['plugins'][$pluginName]['button_label'] : false;
+		
+		if(isset($pluginDefinitions['public']['fields']))
 		{
 		{
+			# add simple honeypot spam protection
+			$pluginDefinitions['public']['fields']['personal-mail'] = ['type' => 'text', 'class' => 'personal-mail'];
+	
+			/* 
+			# add spam protection questions
+			$spamanswers = ['Albert', 'kalt', 'Gelb'];
+			shuffle($spamanswers);
+			$pluginDefinitions['public']['fields']['spamquestion'] = ['type' => 'checkboxlist', 'label' => 'Der Vorname von Einstein lautet', 'required' => true, 'options' => $spamanswers];
+			*/
+				
 			# get all the fields and prefill them with the dafault-data, the user-data or old input data
 			# get all the fields and prefill them with the dafault-data, the user-data or old input data
-			$fields = $fieldsModel->getFields($userSettings = false, 'plugins', $pluginName, $pluginDefinitions, 'frontend');
+			$fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public');
 
 
-			# use the field-objects to generate the html-fields
-			
+			# get Twig Instance
+			$twig 	= $this->getTwig();
+
+			# render each field and add it to the form
+			$form = $twig->fetch('/partials/form.twig', ['fields' => $fields, 'itemName' => $pluginName, 'object' => 'plugins', 'buttonlabel' => $buttonlabel]);
 		}
 		}
+		
+		return $form;
 	}
 	}
 }
 }

+ 3 - 1
system/Routes/Api.php

@@ -21,4 +21,6 @@ $app->delete('/api/v1/block', ContentApiController::class . ':deleteBlock')->set
 $app->put('/api/v1/moveblock', ContentApiController::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
 $app->put('/api/v1/moveblock', ContentApiController::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
 
 
 $app->post('/api/v1/image', ContentApiController::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
 $app->post('/api/v1/image', ContentApiController::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
-$app->put('/api/v1/image', ContentApiController::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
+$app->put('/api/v1/image', ContentApiController::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
+
+$app->post('/api/v1/video', ContentApiController::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router']));

+ 3 - 0
system/Routes/Web.php

@@ -1,6 +1,7 @@
 <?php
 <?php
 
 
 use Typemill\Controllers\PageController;
 use Typemill\Controllers\PageController;
+use Typemill\Controllers\FormController;
 use Typemill\Controllers\SetupController;
 use Typemill\Controllers\SetupController;
 use Typemill\Controllers\AuthController;
 use Typemill\Controllers\AuthController;
 use Typemill\Controllers\SettingsController;
 use Typemill\Controllers\SettingsController;
@@ -27,6 +28,8 @@ else
 	$app->get('/setup/welcome', AuthController::class . ':redirect')->setName('setup.welcome');	
 	$app->get('/setup/welcome', AuthController::class . ':redirect')->setName('setup.welcome');	
 }
 }
 
 
+$app->post('/tm/formpost', FormController::class . ':savePublicForm')->setName('form.save');
+
 $app->get('/tm', AuthController::class . ':redirect');
 $app->get('/tm', AuthController::class . ':redirect');
 $app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router']));
 $app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router']));
 $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router']));
 $app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router']));

+ 0 - 1
system/Settings.php

@@ -88,7 +88,6 @@ class Settings
 		
 		
 		if($userSettings)
 		if($userSettings)
 		{
 		{
-			
 			$yaml 		= new Models\WriteYaml();
 			$yaml 		= new Models\WriteYaml();
 			$settings 	= array_merge($userSettings, $settings);
 			$settings 	= array_merge($userSettings, $settings);
 			
 			

+ 9 - 9
system/author/css/fontello/LICENSE.txt

@@ -1,15 +1,6 @@
 Font license info
 Font license info
 
 
 
 
-## Entypo
-
-   Copyright (C) 2012 by Daniel Bruce
-
-   Author:    Daniel Bruce
-   License:   SIL (http://scripts.sil.org/OFL)
-   Homepage:  http://www.entypo.com
-
-
 ## Font Awesome
 ## Font Awesome
 
 
    Copyright (C) 2016 by Dave Gandy
    Copyright (C) 2016 by Dave Gandy
@@ -19,3 +10,12 @@ Font license info
    Homepage:  http://fortawesome.github.com/Font-Awesome/
    Homepage:  http://fortawesome.github.com/Font-Awesome/
 
 
 
 
+## Entypo
+
+   Copyright (C) 2012 by Daniel Bruce
+
+   Author:    Daniel Bruce
+   License:   SIL (http://scripts.sil.org/OFL)
+   Homepage:  http://www.entypo.com
+
+

+ 29 - 11
system/author/css/fontello/config.json

@@ -7,10 +7,10 @@
   "ascent": 850,
   "ascent": 850,
   "glyphs": [
   "glyphs": [
     {
     {
-      "uid": "c709da589c923ba3c2ad48d9fc563e93",
-      "css": "cancel",
+      "uid": "0f99ab40ab0b4d64a74f2d0deeb03e42",
+      "css": "videocam",
       "code": 59392,
       "code": 59392,
-      "src": "entypo"
+      "src": "fontawesome"
     },
     },
     {
     {
       "uid": "381da2c2f7fd51f8de877c044d7f439d",
       "uid": "381da2c2f7fd51f8de877c044d7f439d",
@@ -18,34 +18,46 @@
       "code": 59393,
       "code": 59393,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
+    {
+      "uid": "c709da589c923ba3c2ad48d9fc563e93",
+      "css": "cancel",
+      "code": 59394,
+      "src": "entypo"
+    },
     {
     {
       "uid": "f9cbf7508cd04145ade2800169959eef",
       "uid": "f9cbf7508cd04145ade2800169959eef",
       "css": "font",
       "css": "font",
-      "code": 59394,
+      "code": 59395,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
       "uid": "e99461abfef3923546da8d745372c995",
       "uid": "e99461abfef3923546da8d745372c995",
       "css": "cog",
       "css": "cog",
-      "code": 59395,
+      "code": 59396,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
       "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
       "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
       "css": "off",
       "css": "off",
-      "code": 59396,
+      "code": 59397,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
       "uid": "d7271d490b71df4311e32cdacae8b331",
       "uid": "d7271d490b71df4311e32cdacae8b331",
       "css": "home",
       "css": "home",
-      "code": 59397,
+      "code": 59398,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
       "uid": "44e04715aecbca7f266a17d5a7863c68",
       "uid": "44e04715aecbca7f266a17d5a7863c68",
       "css": "plus",
       "css": "plus",
-      "code": 59398,
+      "code": 59399,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "e15f0d620a7897e2035c18c80142f6d9",
+      "css": "link-ext",
+      "code": 61582,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
@@ -55,9 +67,9 @@
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
-      "uid": "e15f0d620a7897e2035c18c80142f6d9",
-      "css": "link-ext",
-      "code": 61582,
+      "uid": "6605ee6441bf499ffa3c63d3c7409471",
+      "css": "move",
+      "code": 61511,
       "src": "fontawesome"
       "src": "fontawesome"
     },
     },
     {
     {
@@ -71,6 +83,12 @@
       "css": "folder-empty",
       "css": "folder-empty",
       "code": 61716,
       "code": 61716,
       "src": "fontawesome"
       "src": "fontawesome"
+    },
+    {
+      "uid": "872d9516df93eb6b776cc4d94bd97dac",
+      "css": "video",
+      "code": 59400,
+      "src": "fontawesome"
     }
     }
   ]
   ]
 }
 }

+ 9 - 6
system/author/css/fontello/css/fontello-codes.css

@@ -1,11 +1,14 @@
 
 
-.icon-cancel:before { content: '\e800'; } /* '' */
+.icon-videocam:before { content: '\e800'; } /* '' */
 .icon-picture:before { content: '\e801'; } /* '' */
 .icon-picture:before { content: '\e801'; } /* '' */
-.icon-font:before { content: '\e802'; } /* '' */
-.icon-cog:before { content: '\e803'; } /* '' */
-.icon-off:before { content: '\e804'; } /* '' */
-.icon-home:before { content: '\e805'; } /* '' */
-.icon-plus:before { content: '\e806'; } /* '' */
+.icon-cancel:before { content: '\e802'; } /* '' */
+.icon-font:before { content: '\e803'; } /* '' */
+.icon-cog:before { content: '\e804'; } /* '' */
+.icon-off:before { content: '\e805'; } /* '' */
+.icon-home:before { content: '\e806'; } /* '' */
+.icon-plus:before { content: '\e807'; } /* '' */
+.icon-video:before { content: '\e808'; } /* '' */
+.icon-move:before { content: '\f047'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */

File diff suppressed because it is too large
+ 3 - 3
system/author/css/fontello/css/fontello-embedded.css


+ 9 - 6
system/author/css/fontello/css/fontello-ie7-codes.css

@@ -1,11 +1,14 @@
 
 
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
+.icon-videocam { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
+.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
+.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
+.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
+.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
+.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
 .icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0b2;&nbsp;'); }
 .icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0b2;&nbsp;'); }
 .icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
 .icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }

+ 9 - 6
system/author/css/fontello/css/fontello-ie7.css

@@ -10,13 +10,16 @@
   /* font-size: 120%; */
   /* font-size: 120%; */
 }
 }
  
  
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
+.icon-videocam { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
+.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
+.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
+.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
+.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
+.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
 .icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
 .icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0b2;&nbsp;'); }
 .icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0b2;&nbsp;'); }
 .icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
 .icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }

+ 16 - 13
system/author/css/fontello/css/fontello.css

@@ -1,11 +1,11 @@
 @font-face {
 @font-face {
   font-family: 'fontello';
   font-family: 'fontello';
-  src: url('../font/fontello.eot?52515879');
-  src: url('../font/fontello.eot?52515879#iefix') format('embedded-opentype'),
-       url('../font/fontello.woff2?52515879') format('woff2'),
-       url('../font/fontello.woff?52515879') format('woff'),
-       url('../font/fontello.ttf?52515879') format('truetype'),
-       url('../font/fontello.svg?52515879#fontello') format('svg');
+  src: url('../font/fontello.eot?80940798');
+  src: url('../font/fontello.eot?80940798#iefix') format('embedded-opentype'),
+       url('../font/fontello.woff2?80940798') format('woff2'),
+       url('../font/fontello.woff?80940798') format('woff'),
+       url('../font/fontello.ttf?80940798') format('truetype'),
+       url('../font/fontello.svg?80940798#fontello') format('svg');
   font-weight: normal;
   font-weight: normal;
   font-style: normal;
   font-style: normal;
 }
 }
@@ -15,7 +15,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
   @font-face {
     font-family: 'fontello';
     font-family: 'fontello';
-    src: url('../font/fontello.svg?52515879#fontello') format('svg');
+    src: url('../font/fontello.svg?80940798#fontello') format('svg');
   }
   }
 }
 }
 */
 */
@@ -55,13 +55,16 @@
   /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
   /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
 }
 }
  
  
-.icon-cancel:before { content: '\e800'; } /* '' */
+.icon-videocam:before { content: '\e800'; } /* '' */
 .icon-picture:before { content: '\e801'; } /* '' */
 .icon-picture:before { content: '\e801'; } /* '' */
-.icon-font:before { content: '\e802'; } /* '' */
-.icon-cog:before { content: '\e803'; } /* '' */
-.icon-off:before { content: '\e804'; } /* '' */
-.icon-home:before { content: '\e805'; } /* '' */
-.icon-plus:before { content: '\e806'; } /* '' */
+.icon-cancel:before { content: '\e802'; } /* '' */
+.icon-font:before { content: '\e803'; } /* '' */
+.icon-cog:before { content: '\e804'; } /* '' */
+.icon-off:before { content: '\e805'; } /* '' */
+.icon-home:before { content: '\e806'; } /* '' */
+.icon-plus:before { content: '\e807'; } /* '' */
+.icon-video:before { content: '\e808'; } /* '' */
+.icon-move:before { content: '\f047'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */

+ 17 - 12
system/author/css/fontello/demo.html

@@ -229,11 +229,11 @@ body {
 }
 }
 @font-face {
 @font-face {
       font-family: 'fontello';
       font-family: 'fontello';
-      src: url('./font/fontello.eot?74591350');
-      src: url('./font/fontello.eot?74591350#iefix') format('embedded-opentype'),
-           url('./font/fontello.woff?74591350') format('woff'),
-           url('./font/fontello.ttf?74591350') format('truetype'),
-           url('./font/fontello.svg?74591350#fontello') format('svg');
+      src: url('./font/fontello.eot?44201991');
+      src: url('./font/fontello.eot?44201991#iefix') format('embedded-opentype'),
+           url('./font/fontello.woff?44201991') format('woff'),
+           url('./font/fontello.ttf?44201991') format('truetype'),
+           url('./font/fontello.svg?44201991#fontello') format('svg');
       font-weight: normal;
       font-weight: normal;
       font-style: normal;
       font-style: normal;
     }
     }
@@ -298,19 +298,24 @@ body {
     </div>
     </div>
     <div class="container" id="icons">
     <div class="container" id="icons">
       <div class="row">
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-cancel">&#xe800;</i> <span class="i-name">icon-cancel</span><span class="i-code">0xe800</span></div>
+        <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-videocam">&#xe800;</i> <span class="i-name">icon-videocam</span><span class="i-code">0xe800</span></div>
         <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-picture">&#xe801;</i> <span class="i-name">icon-picture</span><span class="i-code">0xe801</span></div>
         <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-picture">&#xe801;</i> <span class="i-name">icon-picture</span><span class="i-code">0xe801</span></div>
-        <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-font">&#xe802;</i> <span class="i-name">icon-font</span><span class="i-code">0xe802</span></div>
-        <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-cog">&#xe803;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe803</span></div>
+        <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-cancel">&#xe802;</i> <span class="i-name">icon-cancel</span><span class="i-code">0xe802</span></div>
+        <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-font">&#xe803;</i> <span class="i-name">icon-font</span><span class="i-code">0xe803</span></div>
       </div>
       </div>
       <div class="row">
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-off">&#xe804;</i> <span class="i-name">icon-off</span><span class="i-code">0xe804</span></div>
-        <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-home">&#xe805;</i> <span class="i-name">icon-home</span><span class="i-code">0xe805</span></div>
-        <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-plus">&#xe806;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe806</span></div>
-        <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
+        <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-cog">&#xe804;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe804</span></div>
+        <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-off">&#xe805;</i> <span class="i-name">icon-off</span><span class="i-code">0xe805</span></div>
+        <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-home">&#xe806;</i> <span class="i-name">icon-home</span><span class="i-code">0xe806</span></div>
+        <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-plus">&#xe807;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe807</span></div>
       </div>
       </div>
       <div class="row">
       <div class="row">
+        <div class="the-icons span3" title="Code: 0xe808"><i class="demo-icon icon-video">&#xe808;</i> <span class="i-name">icon-video</span><span class="i-code">0xe808</span></div>
+        <div class="the-icons span3" title="Code: 0xf047"><i class="demo-icon icon-move">&#xf047;</i> <span class="i-name">icon-move</span><span class="i-code">0xf047</span></div>
+        <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
         <div class="the-icons span3" title="Code: 0xf0b2"><i class="demo-icon icon-resize-full-alt">&#xf0b2;</i> <span class="i-name">icon-resize-full-alt</span><span class="i-code">0xf0b2</span></div>
         <div class="the-icons span3" title="Code: 0xf0b2"><i class="demo-icon icon-resize-full-alt">&#xf0b2;</i> <span class="i-name">icon-resize-full-alt</span><span class="i-code">0xf0b2</span></div>
+      </div>
+      <div class="row">
         <div class="the-icons span3" title="Code: 0xf0f6"><i class="demo-icon icon-doc-text">&#xf0f6;</i> <span class="i-name">icon-doc-text</span><span class="i-code">0xf0f6</span></div>
         <div class="the-icons span3" title="Code: 0xf0f6"><i class="demo-icon icon-doc-text">&#xf0f6;</i> <span class="i-name">icon-doc-text</span><span class="i-code">0xf0f6</span></div>
         <div class="the-icons span3" title="Code: 0xf114"><i class="demo-icon icon-folder-empty">&#xf114;</i> <span class="i-name">icon-folder-empty</span><span class="i-code">0xf114</span></div>
         <div class="the-icons span3" title="Code: 0xf114"><i class="demo-icon icon-folder-empty">&#xf114;</i> <span class="i-name">icon-folder-empty</span><span class="i-code">0xf114</span></div>
       </div>
       </div>

BIN
system/author/css/fontello/font/fontello.eot


+ 13 - 7
system/author/css/fontello/font/fontello.svg

@@ -1,24 +1,30 @@
 <?xml version="1.0" standalone="no"?>
 <?xml version="1.0" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg xmlns="http://www.w3.org/2000/svg">
 <svg xmlns="http://www.w3.org/2000/svg">
-<metadata>Copyright (C) 2018 by original authors @ fontello.com</metadata>
+<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
 <defs>
 <defs>
 <font id="fontello" horiz-adv-x="1000" >
 <font id="fontello" horiz-adv-x="1000" >
 <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
 <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
 <missing-glyph horiz-adv-x="1000" />
 <missing-glyph horiz-adv-x="1000" />
-<glyph glyph-name="cancel" unicode="&#xe800;" d="M452 194q18-18 18-43t-18-43q-18-16-43-16t-43 16l-132 152-132-152q-18-16-43-16t-43 16q-16 18-16 43t16 43l138 156-138 158q-16 18-16 43t16 43q18 16 43 16t43-16l132-152 132 152q18 16 43 16t43-16q18-18 18-43t-18-43l-138-158z" horiz-adv-x="470" />
+<glyph glyph-name="videocam" unicode="&#xe800;" d="M1000 654v-608q0-23-22-32-7-3-14-3-15 0-25 10l-225 225v-92q0-67-47-114t-113-47h-393q-67 0-114 47t-47 114v392q0 67 47 114t114 47h393q66 0 113-47t47-114v-92l225 225q10 10 25 10 7 0 14-2 22-10 22-33z" horiz-adv-x="1000" />
 
 
 <glyph glyph-name="picture" unicode="&#xe801;" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
 <glyph glyph-name="picture" unicode="&#xe801;" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
 
 
-<glyph glyph-name="font" unicode="&#xe802;" d="M405 538l-95-251q18 0 76-1t89-1q11 0 32 1-48 141-102 252z m-405-617l1 44q13 4 31 7t32 6 28 8 25 17 17 28l132 344 156 404h72q4-8 6-12l114-268q19-43 60-144t63-153q9-19 33-80t40-94q11-26 19-32 11-9 49-17t47-11q4-22 4-32 0-3-1-8t0-7q-35 0-106 5t-107 4q-42 0-120-4t-99-4q0 24 2 43l73 16q1 0 7 1t9 2 8 3 9 4 6 4 5 6 1 8q0 9-17 54t-40 99-24 56l-251 1q-14-32-43-109t-28-91q0-12 8-21t24-14 27-7 32-5 23-2q1-11 1-32 0-5-1-16-33 0-98 6t-97 6q-5 0-15-3t-12-2q-45-8-105-8z" horiz-adv-x="928.6" />
+<glyph glyph-name="cancel" unicode="&#xe802;" d="M452 194q18-18 18-43t-18-43q-18-16-43-16t-43 16l-132 152-132-152q-18-16-43-16t-43 16q-16 18-16 43t16 43l138 156-138 158q-16 18-16 43t16 43q18 16 43 16t43-16l132-152 132 152q18 16 43 16t43-16q18-18 18-43t-18-43l-138-158z" horiz-adv-x="470" />
 
 
-<glyph glyph-name="cog" unicode="&#xe803;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
+<glyph glyph-name="font" unicode="&#xe803;" d="M405 538l-95-251q18 0 76-1t89-1q11 0 32 1-48 141-102 252z m-405-617l1 44q13 4 31 7t32 6 28 8 25 17 17 28l132 344 156 404h72q4-8 6-12l114-268q19-43 60-144t63-153q9-19 33-80t40-94q11-26 19-32 11-9 49-17t47-11q4-22 4-32 0-3-1-8t0-7q-35 0-106 5t-107 4q-42 0-120-4t-99-4q0 24 2 43l73 16q1 0 7 1t9 2 8 3 9 4 6 4 5 6 1 8q0 9-17 54t-40 99-24 56l-251 1q-14-32-43-109t-28-91q0-12 8-21t24-14 27-7 32-5 23-2q1-11 1-32 0-5-1-16-33 0-98 6t-97 6q-5 0-15-3t-12-2q-45-8-105-8z" horiz-adv-x="928.6" />
 
 
-<glyph glyph-name="off" unicode="&#xe804;" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 23-111t61-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-23 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
+<glyph glyph-name="cog" unicode="&#xe804;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
 
 
-<glyph glyph-name="home" unicode="&#xe805;" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" />
+<glyph glyph-name="off" unicode="&#xe805;" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 23-111t61-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-23 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
 
 
-<glyph glyph-name="plus" unicode="&#xe806;" d="M786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
+<glyph glyph-name="home" unicode="&#xe806;" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="plus" unicode="&#xe807;" d="M786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="video" unicode="&#xe808;" d="M214-43v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="move" unicode="&#xf047;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
 
 
 <glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
 <glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
 
 

BIN
system/author/css/fontello/font/fontello.ttf


BIN
system/author/css/fontello/font/fontello.woff


BIN
system/author/css/fontello/font/fontello.woff2


+ 41 - 3
system/author/css/style.css

@@ -227,7 +227,7 @@ li.menu-item{
 	background: #e0474c;
 	background: #e0474c;
 	color: #fff;
 	color: #fff;
 }
 }
-.navi-item i.icon-resize-full-alt{
+.navi-item i.icon-resize-full-alt,.navi-item i.icon-move {
 	position: absolute;
 	position: absolute;
 	right: 5px;
 	right: 5px;
 	top: 7px;
 	top: 7px;
@@ -1618,12 +1618,15 @@ button.format-item:hover{
 	box-sizing:border-box;
 	box-sizing:border-box;
 	padding: 0;
 	padding: 0;
 }
 }
-.dropbox input{
-	background: #fff;
+.dropbox input, .dropbox select{
+	background-color: #fff;
 	width: 80%;
 	width: 80%;
 	margin: 2px 0;
 	margin: 2px 0;
 	display: inline-block;
 	display: inline-block;
 }
 }
+.dropbox select{
+	background-image: linear-gradient(45deg, transparent 50%, #444 50%), linear-gradient(135deg, #444 50%, transparent 50%), linear-gradient(to right, #fff, #fff);
+}
 .dropbox label{
 .dropbox label{
 	width: 20%;
 	width: 20%;
 	display: inline-block;
 	display: inline-block;
@@ -1643,6 +1646,41 @@ button.format-item:hover{
 	margin: auto;
 	margin: auto;
 	max-width: 100%;
 	max-width: 100%;
 }
 }
+.blox img.youtube{
+	position: relative;
+}
+.blox .video-container{
+	position: relative;
+	text-align: center;
+}
+.blox button.play-video { 
+  position: absolute;
+  top: 50%;
+  margin-top: -50px;
+  margin-left: -50px;
+  height: 100px;
+  width: 100px;
+  background: #e0474c;
+  color: #FFFFFF;
+  border-radius: 50%;
+  border: 0px;
+  padding: 0;
+  text-align: center;
+}
+.blox button.play-video:hover {
+  background: #cc4146;
+}
+.blox button.play-video::after {
+  position: absolute;
+  top: 50%;
+  margin: -20px 0 0 -15px;
+  height: 0;
+  width: 0;
+  border-style: solid;
+  border-width: 20px 0 20px 40px;
+  border-color: transparent transparent transparent rgba(255, 255, 255, 0.75);
+  content: ' ';
+}
 sup{}
 sup{}
 cite{}
 cite{}
 abbr{}
 abbr{}

+ 1 - 0
system/author/editor/editor-blox.twig

@@ -29,6 +29,7 @@
 					<content-block :body="false">
 					<content-block :body="false">
 						<button class="format-item" @click.prevent="setData( $event, 'markdown-component' )" data-id="99999" id="blox-99999"><i class="icon-font"></i></button>
 						<button class="format-item" @click.prevent="setData( $event, 'markdown-component' )" data-id="99999" id="blox-99999"><i class="icon-font"></i></button>
 						<button class="format-item" @click.prevent="setData( $event, 'image-component' )" data-id="99999" id="blox-99999"><i class="icon-picture"></i></button>
 						<button class="format-item" @click.prevent="setData( $event, 'image-component' )" data-id="99999" id="blox-99999"><i class="icon-picture"></i></button>
+						<button class="format-item" @click.prevent="setData( $event, 'video-component' )" data-id="99999" id="blox-99999"><i class="icon-video"></i></button>
 					</content-block>
 					</content-block>
 				</div>
 				</div>
 				
 				

+ 34 - 0
system/author/js/lazy-video.js

@@ -0,0 +1,34 @@
+( function() {
+
+    var youtube = document.querySelectorAll( ".youtube" );
+    
+    for (var i = 0; i < youtube.length; i++)
+	{
+		var thisyoutube = youtube[i];		
+		thisyoutube.parentNode.classList.add("video-container");
+		
+		var playbutton = document.createElement("button");
+		playbutton.classList.add("play-video");
+		playbutton.value = "Play";
+		
+		thisyoutube.parentNode.appendChild(playbutton);
+		
+		playbutton.addEventListener( "click", function(event)
+		{
+			event.preventDefault();
+			event.stopPropagation();
+			
+			var iframe = document.createElement( "iframe" );
+
+			iframe.setAttribute( "frameborder", "0" );
+			iframe.setAttribute( "allowfullscreen", "" );
+			iframe.setAttribute( "width", "560" );
+			iframe.setAttribute( "height", "315" );
+			iframe.setAttribute( "src", "https://www.youtube.com/embed/" + thisyoutube.id + "?rel=0&showinfo=0&autoplay=1" );
+
+			var videocontainer = thisyoutube.parentNode
+			videocontainer.innerHTML = "";
+			videocontainer.appendChild( iframe );
+		})(thisyoutube);
+    };
+} )();

+ 114 - 15
system/author/js/vue-blox.js

@@ -22,7 +22,7 @@ const contentComponent = Vue.component('content-block', {
 			this.compmarkdown = $event;
 			this.compmarkdown = $event;
 			this.$nextTick(function () {
 			this.$nextTick(function () {
 				this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
 				this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
-			});			
+			});
 		},
 		},
 		switchToEditMode: function()
 		switchToEditMode: function()
 		{
 		{
@@ -35,9 +35,22 @@ const contentComponent = Vue.component('content-block', {
 			this.edit = true;										/* show the edit-mode */
 			this.edit = true;										/* show the edit-mode */
 			this.compmarkdown = self.$root.$data.blockMarkdown;		/* get markdown data */
 			this.compmarkdown = self.$root.$data.blockMarkdown;		/* get markdown data */
 			this.componentType = self.$root.$data.blockType;		/* get block-type of element */
 			this.componentType = self.$root.$data.blockType;		/* get block-type of element */
-			this.$nextTick(function () {
-				this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
-			});
+			if(this.componentType == 'image-component')
+			{
+				setTimeout(function(){ 
+					self.$nextTick(function () 
+					{
+						self.$refs.preview.style.minHeight = self.$refs.component.offsetHeight + 'px';
+					});
+				}, 200);				
+			}
+			else
+			{
+				this.$nextTick(function () 
+				{
+					this.$refs.preview.style.minHeight = self.$refs.component.offsetHeight + 'px';
+				});				
+			}
 		},
 		},
 		closeComponents: function()
 		closeComponents: function()
 		{
 		{
@@ -80,7 +93,7 @@ const contentComponent = Vue.component('content-block', {
 			if(self.$root.$data.blockType != '')
 			if(self.$root.$data.blockType != '')
 			{
 			{
 				this.switchToEditMode();
 				this.switchToEditMode();
-			}			
+			}
 		},
 		},
  		submitBlock: function(){
  		submitBlock: function(){
 			var emptyline = /^\s*$(?:\r\n?|\n)/gm;
 			var emptyline = /^\s*$(?:\r\n?|\n)/gm;
@@ -99,8 +112,6 @@ const contentComponent = Vue.component('content-block', {
 		},
 		},
 		saveBlock: function()
 		saveBlock: function()
 		{	
 		{	
-			console.log(this.compmarkdown);
-		
 			if(this.compmarkdown == undefined || this.compmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '')
 			if(this.compmarkdown == undefined || this.compmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '')
 			{
 			{
 				this.switchToPreviewMode();	
 				this.switchToPreviewMode();	
@@ -116,6 +127,11 @@ const contentComponent = Vue.component('content-block', {
 					var url = self.$root.$data.root + '/api/v1/image';
 					var url = self.$root.$data.root + '/api/v1/image';
 					var method 	= 'PUT';
 					var method 	= 'PUT';
 				}
 				}
+				else if(this.componentType == 'video-component')
+				{
+					var url = self.$root.$data.root + '/api/v1/video';
+					var method = 'POST';
+				}
 				else
 				else
 				{
 				{
 					var url = self.$root.$data.root + '/api/v1/block';
 					var url = self.$root.$data.root + '/api/v1/block';
@@ -268,7 +284,20 @@ const titleComponent = Vue.component('title-component', {
 
 
 const imageComponent = Vue.component('image-component', {
 const imageComponent = Vue.component('image-component', {
 	props: ['compmarkdown', 'disabled'],
 	props: ['compmarkdown', 'disabled'],
-	template: '<div class="dropbox"><input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown"><input type="file" name="image" accept="image/*" class="input-file" @change="onFileChange( $event )"><p>drag a picture or click to select</p><img class="uploadPreview" :src="imgpreview" /><div v-if="load" class="loadwrapper"><span class="load"></span></div><div class="imgmeta" v-if="imgmeta"><label for="imgalt">Alt-Text: </label><input name="imgalt" type="text" placeholder="alt" @input="createmarkdown" v-model="imgalt" max="100"/><label for="imgtitle">Title: </label><input name="imgtitle" type="text" placeholder="title" v-model="imgtitle" @input="createmarkdown" max="64" /><label for="imgcaption">Caption: </label><input title="imgcaption" type="text" placeholder="caption" v-model="imgcaption" @input="createmarkdown" max="140" /><label for="imgurl">Link: </label><input title="imgurl" type="url" placeholder="url" v-model="imglink" @input="createmarkdown" /></div></div>',
+	template: '<div class="dropbox">' +
+				'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
+				'<input type="file" name="image" accept="image/*" class="input-file" @change="onFileChange( $event )" /> ' +
+				'<p>drag a picture or click to select</p>' +
+				'<img class="uploadPreview" :src="imgpreview" />' +
+				'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
+				'<div class="imgmeta" v-if="imgmeta">' +
+				'<label for="imgalt">Alt-Text: </label><input name="imgalt" type="text" placeholder="alt" @input="createmarkdown" v-model="imgalt" max="100" />' +
+				'<label for="imgtitle">Title: </label><input name="imgtitle" type="text" placeholder="title" v-model="imgtitle" @input="createmarkdown" max="64" />' +
+				'<label for="imgcaption">Caption: </label><input title="imgcaption" type="text" placeholder="caption" v-model="imgcaption" @input="createmarkdown" max="140" />' +
+				'<label for="imgurl">Link: </label><input title="imgurl" type="url" placeholder="url" v-model="imglink" @input="createmarkdown" />' +
+				'<label for="imgclass">Class: </label><select title="imgclass" v-model="imgclass" @change="createmarkdown"><option value="center">Center</option><option value="left">Left</option><option value="right">Right</option><option value="youtube">Youtube</option><option value="vimeo">Vimeo</option></select>' +
+				'<input title="imgid" type="hidden" placeholder="id" v-model="imgid" @input="createmarkdown" max="140" />' +
+				'</div></div>',
 	data: function(){
 	data: function(){
 		return {
 		return {
 			maxsize: 5, // megabyte
 			maxsize: 5, // megabyte
@@ -279,19 +308,21 @@ const imageComponent = Vue.component('image-component', {
 			imgtitle: '',
 			imgtitle: '',
 			imgcaption: '',
 			imgcaption: '',
 			imglink: '',
 			imglink: '',
+			imgclass: 'center',
+			imgid: '',
 			imgfile: 'imgplchldr',
 			imgfile: 'imgplchldr',
 		}
 		}
 	},
 	},
 	mounted: function(){
 	mounted: function(){
-		// autosize(document.querySelector('textarea'));
-		this.$refs.markdown.focus();
 		
 		
+		this.$refs.markdown.focus();
+				
 		if(this.compmarkdown)
 		if(this.compmarkdown)
-		{			
+		{
 			this.imgmeta = true;
 			this.imgmeta = true;
 			
 			
 			var imgmarkdown = this.compmarkdown;
 			var imgmarkdown = this.compmarkdown;
-						
+									
 			var imgcaption = imgmarkdown.match(/\*.*?\*/);
 			var imgcaption = imgmarkdown.match(/\*.*?\*/);
 			if(imgcaption){
 			if(imgcaption){
 				this.imgcaption = imgcaption[0].slice(1,-1);
 				this.imgcaption = imgcaption[0].slice(1,-1);
@@ -322,6 +353,25 @@ const imageComponent = Vue.component('image-component', {
 			{
 			{
 				this.imgalt = imgalt[0].slice(1,-1);
 				this.imgalt = imgalt[0].slice(1,-1);
 			}
 			}
+
+			var imgattr = imgmarkdown.match(/\{.*?\}/);
+			if(imgattr)
+			{
+				imgattr = imgattr[0].slice(1,-1);
+				imgattr = imgattr.split(' ');
+				for (var i = 0; i < imgattr.length; i++)
+				{
+					if(imgattr[i].charAt(0) == '.')
+					{
+						this.imgclass = imgattr[i].slice(1);
+					}
+					else if(imgattr[i].charAt(0)  == '#')
+					{
+						this.imgid = imgattr[i].slice(1);
+					}
+				}
+			}
+
 			var imgpreview = imgmarkdown.match(/\(.*?\)/);
 			var imgpreview = imgmarkdown.match(/\(.*?\)/);
 			if(imgpreview)
 			if(imgpreview)
 			{
 			{
@@ -331,13 +381,20 @@ const imageComponent = Vue.component('image-component', {
 		}
 		}
 	},
 	},
 	methods: {
 	methods: {
+		isChecked: function(classname)
+		{
+			if(this.imgclass == classname)
+			{
+				return ' checked';
+			}
+		},
 		updatemarkdown: function(event)
 		updatemarkdown: function(event)
 		{
 		{
 			this.$emit('updatedMarkdown', event.target.value);
 			this.$emit('updatedMarkdown', event.target.value);
 		},
 		},
 		createmarkdown: function()
 		createmarkdown: function()
 		{
 		{
-			errors = false;
+			var errors = false;
 			
 			
 			if(this.imgalt.length < 101)
 			if(this.imgalt.length < 101)
 			{
 			{
@@ -365,6 +422,34 @@ const imageComponent = Vue.component('image-component', {
 				imgmarkdown = imgmarkdown + '(' + this.imgfile + ')';		
 				imgmarkdown = imgmarkdown + '(' + this.imgfile + ')';		
 			}
 			}
 			
 			
+			var imgattr = '';
+			if(this.imgid != '')
+			{
+				if(this.imgid.length < 100)
+				{
+					imgattr = imgattr + '#' + this.imgid + ' '; 
+				}
+				else
+				{
+					errors = 'Maximum size of image id is 100 characters';
+				}
+			}
+			if(this.imgclass != '')
+			{
+				if(this.imgclass.length < 100)
+				{
+					imgattr = imgattr + '.' + this.imgclass; 
+				}
+				else
+				{
+					errors = 'Maximum size of image class is 100 characters';
+				}
+			}
+			if(this.imgid != '' || this.imgclass != '')
+			{
+				imgmarkdown = imgmarkdown + '{' + imgattr + '}';
+			}
+			
 			if(this.imglink != '')
 			if(this.imglink != '')
 			{
 			{
 				if(this.imglink.length < 101)
 				if(this.imglink.length < 101)
@@ -376,7 +461,7 @@ const imageComponent = Vue.component('image-component', {
 					errors = 'Maximum size of image link is 100 characters';
 					errors = 'Maximum size of image link is 100 characters';
 				}
 				}
 			}
 			}
-			
+						
 			if(this.imgcaption != '')
 			if(this.imgcaption != '')
 			{
 			{
 				if(this.imgcaption.length < 140)
 				if(this.imgcaption.length < 140)
@@ -388,7 +473,7 @@ const imageComponent = Vue.component('image-component', {
 					errors = 'Maximum size of image caption is 140 characters';
 					errors = 'Maximum size of image caption is 140 characters';
 				}
 				}
 			}
 			}
-			
+						
 			if(errors)
 			if(errors)
 			{
 			{
 				this.$parent.freezePage();
 				this.$parent.freezePage();
@@ -472,6 +557,20 @@ const imageComponent = Vue.component('image-component', {
 	}
 	}
 })
 })
 
 
+const videoComponent = Vue.component('video-component', {
+	props: ['compmarkdown', 'disabled', 'load'],
+	template: '<div class="video dropbox">' +
+				'<label for="video">Link to video: </label><input type="url" ref="markdown" placeholder="https://www.youtube.com/watch?v=" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown">' +
+				'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
+				'</div>',
+	methods: {
+		updatemarkdown: function(event)
+		{
+			this.$emit('updatedMarkdown', event.target.value);
+		},
+	},
+})
+
 const tableComponent = Vue.component('table', { 
 const tableComponent = Vue.component('table', { 
 	template: '<div>table component</div>', 
 	template: '<div>table component</div>', 
 })
 })

+ 10 - 9
system/author/layouts/layoutBlox.twig

@@ -19,7 +19,7 @@
 		
 		
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/fontello/css/fontello.css" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/fontello/css/fontello.css" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" />
-		<link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20181206" />
+		<link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20190103" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" />
 		<link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" />
 	</head>
 	</head>
 	<body>	
 	<body>	
@@ -36,13 +36,14 @@
 			</article>
 			</article>
 			<footer></footer>		
 			<footer></footer>		
 		</div>
 		</div>
-		<script src="{{ base_url }}/system/author/js/vue.min.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/autosize.min.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/sortable.min.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/vuedraggable.min.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/author.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/vue-publishcontroller.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/vue-blox.js?20181206"></script>
-		<script src="{{ base_url }}/system/author/js/vue-navi.js?20181206"></script>
+		<script src="{{ base_url }}/system/author/js/vue.min.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/autosize.min.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/sortable.min.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/vuedraggable.min.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/author.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/vue-publishcontroller.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/vue-blox.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/vue-navi.js?20190103"></script>
+		<script src="{{ base_url }}/system/author/js/lazy-video.js?20190103"></script>
 	</body>
 	</body>
 </html>
 </html>

+ 1 - 1
system/author/partials/editorNavi.twig

@@ -42,7 +42,7 @@
 
 
 {% verbatim %}
 {% verbatim %}
 	<template id="navigation-template">
 	<template id="navigation-template">
-		<li class="navi-item" :class="elementtype"><a v-bind:href="getUrl(root, url)" :class="checkActive(active,parent)"><i :class="getIcon(elementtype, filetype)"></i><span :class="getLevel(level)">{{ name }}</span><i class="icon-resize-full-alt"></i></a>
+		<li class="navi-item" :class="elementtype"><a v-bind:href="getUrl(root, url)" :class="checkActive(active,parent)"><i :class="getIcon(elementtype, filetype)"></i><span :class="getLevel(level)">{{ name }}</span><i class="icon-move"></i></a>
 			<draggable v-if="folder" :element="'ul'" class="navi-list" :list="folder" :move="checkMove" @start="onStart" @end="onEnd" :options="{group:{ name:'file'}, animation: 150, 'disabled': freeze }">
 			<draggable v-if="folder" :element="'ul'" class="navi-list" :list="folder" :move="checkMove" @start="onStart" @end="onEnd" :options="{group:{ name:'file'}, animation: 150, 'disabled': freeze }">
 				<navigation ref="draggit" v-for="item in folder" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :url="item.urlRelWoF" :root="root" v-bind:id="item.keyPath" :key="item.keyPath" :filetype="item.fileType" :elementtype="item.elementType" :folder="item.folderContent"></navigation>
 				<navigation ref="draggit" v-for="item in folder" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :url="item.urlRelWoF" :root="root" v-bind:id="item.keyPath" :key="item.keyPath" :filetype="item.fileType" :elementtype="item.elementType" :folder="item.folderContent"></navigation>
 			</draggable>
 			</draggable>

+ 73 - 0
system/author/partials/fields.twig

@@ -0,0 +1,73 @@
+<div class="cardField{{ errors[itemName][field.name] ? ' error' : '' }}">
+				
+	<label for="{{ itemName }}[{{ field.name }}]">{{ field.getLabel() }}
+		{% if field.getAttribute('required') %}<strong><abbr title="required">*</abbr></strong>{% endif %}
+		{% if field.help %}<div class="help">?<span class="tooltip">{{field.help|slice(0,100)}}</span></div>{% endif %}
+	</label>
+
+	{% if field.type == 'textarea' %}
+
+		<textarea name="{{ itemName }}[{{ field.name }}]"{{field.getAttributeValues() }}{{ field.getAttributes() }}>{{ field.getContent() }}</textarea>
+
+	{% elseif field.type == 'paragraph' %}
+		
+		{{ markdown(field.getContent()) }}	
+
+	{% elseif field.type == 'checkbox' %}
+	
+		{{ field.getAttributes }}
+		
+		<label class="control-group">{{ field.getCheckboxLabel() }}
+			<input type="checkbox" name="{{ itemName}}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
+			<span class="checkmark"></span>
+		</label>
+
+	{% elseif field.type == 'checkboxlist' %}
+
+		{% set options = field.getOptions() %}
+
+		{% for value,label in options %}
+			
+			<label class="control-group">{{ label }}
+				<input type="checkbox" name="{{ itemName }}[{{ field.name }}][{{value}}]" {{ settings[object][itemName][field.name][value] ? ' checked' : '' }}>
+				<span class="checkmark"></span>
+			</label>
+
+		{% endfor %}
+
+	{% elseif field.type == 'select' %}
+
+		{% set options = field.getOptions() %}
+
+		<select name="{{ itemName }}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
+			{% for value,label in options %}
+				<option value="{{ value }}" {{ (value == field.getAttributeValue('value')) ? ' selected' : '' }}>{{ label }}</option>
+			{% endfor %}
+		</select>
+
+	{% elseif field.type == 'radio' %}
+
+		{% set options = field.getOptions() %}
+
+		{% for value,label in options %}
+			
+			<label class="control-group">{{ label }} 
+				<input type="radio" name="{{ itemName }}[{{ field.name }}]" value="{{ value }}" {{ (value == settings[object][itemName][field.name]) ? ' checked' : '' }}>
+				<span class="radiomark"></span>
+			</label>
+
+		{% endfor %}
+		
+	{% else %}
+
+		<input name="{{itemName}}[{{ field.name }}]" type="{{ field.type }}"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
+
+	{% endif %}
+
+	{% if field.description %}<div class="description">{{field.description}}</div>{% endif %}
+	
+	{% if errors[itemName][field.name] %}
+		<span class="error">{{ errors[itemName][field.name] | first }}</span>
+	{% endif %}
+
+</div>

+ 32 - 0
system/author/partials/form.twig

@@ -0,0 +1,32 @@
+<form method="POST" action="{{ path_for('form.save') }}">
+
+	<fieldset class="card{{ errors[itemName] ? ' errors' : '' }}">
+
+		{% for field in fields %}
+				
+			{% if field.type == 'fieldset' %}
+
+				<fieldset class="subfield">
+					<legend>{{ field.legend }}</legend>
+					{% for field in field.fields %}
+					
+						{% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object } %}
+					
+					{% endfor %}
+				</fieldset>
+
+			{% else %}
+
+				{% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object } %}
+
+			{% endif %}
+
+		{% endfor %}
+				
+		{{ csrf_field() | raw }}
+
+		<input type="submit" value="{{ buttonlabel ? buttonlabel : 'send' }}" />
+		
+		<style>.personal-mail{display:none}</style>
+	</fieldset>
+</form>

+ 0 - 66
system/author/partials/forms.twig

@@ -1,66 +0,0 @@
-
-	<div class="cardField{{ errors[itemName][field.name] ? ' error' : '' }}">
-		
-		<label for="{{ itemName }}[{{ field.name }}]">{{ field.getLabel() }}
-			{% if field.getAttribute('required') %}<strong><abbr title="required">*</abbr></strong>{% endif %}
-			{% if field.help %}<div class="help">?<span class="tooltip">{{field.help|slice(0,100)}}</span></div>{% endif %}
-		</label>
-
-		{% if field.type == 'textarea' %}
-
-			<textarea name="{{ itemName }}[{{ field.name }}]"{{field.getAttributeValues() }}{{ field.getAttributes() }}>{{ field.getContent() }}</textarea>
-
-		{% elseif field.type == 'checkbox' %}
-			
-			<label class="control-group">{{ field.description }}
-				<input type="checkbox" name="{{ itemName}}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
-				<span class="checkmark"></span>
-			</label>							
-
-		{% elseif field.type == 'checkboxlist' %}
-
-			{% set options = field.getOptions() %}
-
-			{% for value,label in options %}
-				
-				<label class="control-group">{{ label }}
-					<input type="checkbox" name="{{ itemName }}[{{ field.name }}][{{value}}]" {{ settings[object][itemName][field.name][value] ? ' checked' : '' }}>
-					<span class="checkmark"></span>
-				</label>
-
-			{% endfor %}
-
-		{% elseif field.type == 'select' %}
-
-			{% set options = field.getOptions() %}
-						
-			<select name="{{ itemName }}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
-				{% for value,label in options %}
-					<option value="{{ value }}" {{ (value == field.getAttributeValue('value')) ? ' selected' : '' }}>{{ label }}</option>
-				{% endfor %}
-			</select>
-
-		{% elseif field.type == 'radio' %}
-
-			{% set options = field.getOptions() %}
-
-			{% for value,label in options %}
-				
-				<label class="control-group">{{ label }} 
-					<input type="radio" name="{{ itemName }}[{{ field.name }}]" value="{{ value }}" {{ (value == settings[object][itemName][field.name]) ? ' checked' : '' }}>
-					<span class="radiomark"></span>
-				</label>
-
-			{% endfor %}
-
-		{% else %}
-
-			<input name="{{itemName}}[{{ field.name }}]" type="{{ field.type }}"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
-
-		{% endif %}
-
-		{% if errors[itemName][field.name] %}
-			<span class="error">{{ errors[itemName][field.name] | first }}</span>
-		{% endif %}
-
-	</div>

+ 1 - 1
system/author/settings/plugins.twig

@@ -43,7 +43,7 @@
 						
 						
 							{% for field in plugin.forms.fields %}
 							{% for field in plugin.forms.fields %}
 								
 								
-								{% include '/partials/forms.twig' with {'itemName' : pluginName, 'object' : 'plugins' } %}
+								{% include '/partials/fields.twig' with {'itemName' : pluginName, 'object' : 'plugins' } %}
 								
 								
 							{% endfor %}
 							{% endfor %}
 							
 							

+ 3 - 5
system/author/settings/themes.twig

@@ -50,13 +50,13 @@
 											<fieldset class="subfield">
 											<fieldset class="subfield">
 												<legend>{{ field.legend }}</legend>
 												<legend>{{ field.legend }}</legend>
 												{% for field in field.fields %}
 												{% for field in field.fields %}
-													{% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
+													{% include '/partials/fields.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
 												{% endfor %}
 												{% endfor %}
 											</fieldset>
 											</fieldset>
 											
 											
 										{% else %}
 										{% else %}
 										
 										
-											{% include '/partials/forms.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
+											{% include '/partials/fields.twig' with {'itemName' : themeName, 'object' : 'themes' } %}
 								
 								
 										{% endif %}
 										{% endif %}
 								
 								
@@ -70,7 +70,7 @@
 										<button type="button" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
 										<button type="button" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
 									</div>
 									</div>
 									<div class="medium">
 									<div class="medium">
-										<input type="submit" value="Save Theme" />								
+										<input type="submit" value="Save Theme" />
 									</div>
 									</div>
 								</div>								
 								</div>								
 							</div>
 							</div>
@@ -85,8 +85,6 @@
 
 
 			</section>
 			</section>
 			
 			
-		</form>
-	
 	</div>
 	</div>
 	
 	
 {% endblock %}
 {% endblock %}

+ 6 - 2
themes/typemill/chapter.twig

@@ -1,7 +1,11 @@
 <div class="chapter">
 <div class="chapter">
 	
 	
-	<div class="chapterNumber">{{ settings.themes.typemill.chapter ? settings.themes.typemill.chapter : 'Chapter'}} {{ item.chapter }}</div>
-
+	{% if settings.themes.typemill.chapter %}
+	
+		<div class="chapterNumber">{{ settings.themes.typemill.chapter }} {{ item.chapter }}</div>
+	
+	{% endif %}
+	
 	{% if content is empty %}
 	{% if content is empty %}
 	
 	
 		<h1>{{ item.name }}</h1>
 		<h1>{{ item.name }}</h1>

+ 40 - 3
themes/typemill/css/style.css

@@ -339,7 +339,44 @@ article img.middle{
 	max-width: 100%;
 	max-width: 100%;
 	margin: auto;
 	margin: auto;
 }
 }
-
+article img.youtube{
+	position: relative;
+	max-width: 560px;
+}
+article .video-container{
+	position: relative;
+	text-align: center;
+}
+article button.play-video { 
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	margin-top: -50px;
+	margin-left: -50px;
+	height: 100px;
+	width: 100px;
+	background: #e0474c;
+	color: #FFFFFF;
+	border-radius: 50%;
+	border: 0px;
+	padding: 0;
+	text-align: center;
+}
+article button.play-video:hover {
+	background: #cc4146;
+	cursor: pointer;
+}
+article button.play-video::after {
+	position: absolute;
+	top: 50%;
+	margin: -20px 0 0 -15px;
+	height: 0;
+	width: 0;
+	border-style: solid;
+	border-width: 20px 0 20px 40px;
+	border-color: transparent transparent transparent rgba(255, 255, 255, 0.75);
+	content: ' ';
+}
 
 
 /************************
 /************************
 *  	PAGING / BREADCRUMB *
 *  	PAGING / BREADCRUMB *
@@ -730,8 +767,8 @@ img.myClass{
 	header p{
 	header p{
 		margin: 20px 0;
 		margin: 20px 0;
 	}
 	}
-	.chapterNumber{
-		margin:	40px 0px 0px;
+	.chapter{
+		margin:	60px 0px 0px;
 	}
 	}
 	.close{ 
 	.close{ 
 		display: block;
 		display: block;

+ 55 - 0
themes/typemill/js/script.js

@@ -0,0 +1,55 @@
+var menu = document.getElementById("menu"),
+	navi = document.getElementById("navigation");
+				
+if(menu)
+{
+	menu.addEventListener("click", function()
+	{
+		if(navi.className == "close")
+		{
+			navi.className = "open";
+			menu.className = "active";
+		}
+		else
+		{
+			navi.className = "close";
+			menu.className = "";
+		}
+	});
+}
+
+var shareButton = document.getElementById("share-button");
+var	shareIcons = document.getElementById("share-icons");
+	
+if(shareButton)
+{
+	shareButton.addEventListener("click", function()
+	{
+		if(shareIcons.className == "share-icons show")
+		{
+			shareIcons.className = "share-icons hide";
+		}
+		else
+		{
+			shareIcons.className = "share-icons show";
+		}
+	});
+}
+
+var shareButtonBottom = document.getElementById("share-button-bottom");
+var	shareIconsBottom = document.getElementById("share-icons-bottom");
+
+if(shareButtonBottom)
+{
+	shareButtonBottom.addEventListener("click", function()
+	{
+		if(shareIconsBottom.className == "share-icons show")
+		{
+			shareIconsBottom.className = "share-icons hide";
+		}
+		else
+		{
+			shareIconsBottom.className = "share-icons show";
+		}
+	});
+}

+ 4 - 55
themes/typemill/partials/layout.twig

@@ -55,62 +55,11 @@
 				{% include 'partials/footer.twig' %}
 				{% include 'partials/footer.twig' %}
 			</footer>
 			</footer>
 		</div>
 		</div>
-		{% block javascripts %}		
-			<script>
-				var menu = document.getElementById("menu"),
-					navi = document.getElementById("navigation");
-				
-				if(menu)
-				{
-					menu.addEventListener("click", function(){
-						if(navi.className == "close")
-						{
-							navi.className = "open";
-							menu.className = "active";
-						}
-						else
-						{
-							navi.className = "close";
-							menu.className = "";
-						}
-					});
-				}
-				
-				var shareButton = document.getElementById("share-button");
-				var	shareIcons = document.getElementById("share-icons");
-					
-				if(shareButton)
-				{
-					shareButton.addEventListener("click", function(){
-						if(shareIcons.className == "share-icons show")
-						{
-							shareIcons.className = "share-icons hide";
-						}
-						else
-						{
-							shareIcons.className = "share-icons show";
-						}
-					});
-				}
-				
-				var shareButtonBottom = document.getElementById("share-button-bottom");
-				var	shareIconsBottom = document.getElementById("share-icons-bottom");
+		{% block javascripts %}
+
+			<script src="{{ base_url }}/themes/typemill/js/script.js"></script>
+			<script src="{{ base_url }}/system/author/js/lazy-video.js"></script>
 
 
-				if(shareButtonBottom)
-				{
-					shareButtonBottom.addEventListener("click", function(){
-						if(shareIconsBottom.className == "share-icons show")
-						{
-							shareIconsBottom.className = "share-icons hide";
-						}
-						else
-						{
-							shareIconsBottom.className = "share-icons show";
-						}
-					});
-				}
-			</script>
-			
 			{{ assets.renderJS() }}
 			{{ assets.renderJS() }}
 		
 		
 		{% endblock %}		
 		{% endblock %}		

+ 1 - 2
themes/typemill/typemill.yaml

@@ -1,5 +1,5 @@
 name: Typemill Theme
 name: Typemill Theme
-version: 1.1.1
+version: 1.1.2
 description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. No JavaScript is used. 
 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
 author: Sebastian Schürmanns
 homepage: https://typemill.net
 homepage: https://typemill.net
@@ -23,7 +23,6 @@ forms:
       type: text
       type: text
       label: Text For Chapter
       label: Text For Chapter
       placeholder: Add Name for Chapter
       placeholder: Add Name for Chapter
-      required: true
 
 
     start:
     start:
       type: text
       type: text

BIN
typemill-1.2.8-f.zip


Some files were not shown because too many files changed in this diff