Browse Source

Version 1.2.8 Image Management

Sebastian 6 years ago
parent
commit
979ebebe1b
41 changed files with 1034 additions and 313 deletions
  1. 1 1
      cache/lastCache.txt
  2. 0 0
      content/01-Welcome/00-Setup.md
  3. 0 0
      content/01-Welcome/01-Write-Content.md
  4. 0 0
      content/01-Welcome/02-Get-Help.md
  5. 0 0
      content/01-Welcome/index.md
  6. 2 1
      content/index.md
  7. BIN
      media/original/5bf950ebf2abc-original.png
  8. 12 1
      readme.md
  9. 57 1
      system/Controllers/ContentApiController.php
  10. 11 6
      system/Controllers/ContentBackendController.php
  11. 7 1
      system/Controllers/SettingsController.php
  12. 2 2
      system/Extensions/ParsedownExtension.php
  13. 325 0
      system/Models/ProcessImage.php
  14. 4 1
      system/Routes/Api.php
  15. 10 8
      system/Settings.php
  16. 4 3
      system/author/auth/login.twig
  17. 9 0
      system/author/css/fontello/LICENSE.txt
  18. 22 34
      system/author/css/fontello/config.json
  19. 7 9
      system/author/css/fontello/css/fontello-codes.css
  20. 3 3
      system/author/css/fontello/css/fontello-embedded.css
  21. 7 9
      system/author/css/fontello/css/fontello-ie7-codes.css
  22. 7 9
      system/author/css/fontello/css/fontello-ie7.css
  23. 14 16
      system/author/css/fontello/css/fontello.css
  24. 13 17
      system/author/css/fontello/demo.html
  25. BIN
      system/author/css/fontello/font/fontello.eot
  26. 7 11
      system/author/css/fontello/font/fontello.svg
  27. BIN
      system/author/css/fontello/font/fontello.ttf
  28. BIN
      system/author/css/fontello/font/fontello.woff
  29. BIN
      system/author/css/fontello/font/fontello.woff2
  30. 101 21
      system/author/css/style.css
  31. 5 4
      system/author/editor/editor-blox.twig
  32. 15 0
      system/author/js/author.js
  33. 371 154
      system/author/js/vue-blox.js
  34. 2 0
      system/author/layouts/layout.twig
  35. 2 0
      system/author/layouts/layoutAuth.twig
  36. 2 0
      system/author/layouts/layoutBlank.twig
  37. 2 0
      system/author/layouts/layoutBlox.twig
  38. 2 0
      system/author/layouts/layoutEditor.twig
  39. 2 0
      themes/typemill/partials/layout.twig
  40. 2 0
      themes/typemill/partials/layoutCover.twig
  41. 4 1
      themes/typemill/typemill.yaml

+ 1 - 1
cache/lastCache.txt

@@ -1 +1 @@
-1541856493
+1543829895

+ 0 - 0
content/00-Welcome/00-Setup.md → content/01-Welcome/00-Setup.md


+ 0 - 0
content/00-Welcome/02-Write-Content.md → content/01-Welcome/01-Write-Content.md


+ 0 - 0
content/00-Welcome/03-Get-Help.md → content/01-Welcome/02-Get-Help.md


+ 0 - 0
content/00-Welcome/index.md → content/01-Welcome/index.md


+ 2 - 1
content/index.md

@@ -2,4 +2,5 @@
 
 **MODERN WEB PUBLISHING FOR WRITERS**
 
-*Typemill is a user-friendly and lightweight open source CMS for publishing text-works like prosa, lyrics, manuals, documentations, studies and more. Just download and start.*
+*Typemill is a user-friendly and lightweight open source CMS for publishing text-works like prosa, lyrics, manuals, documentations, studies and more. Just download and start.*
+

BIN
media/original/5bf950ebf2abc-original.png


+ 12 - 1
readme.md

@@ -45,7 +45,18 @@ The GitHub-version has no vendor-folder, so you have to update and include all l
 
 If you did not use composer before, please go to the [composer website](http://getcomposer.org) and start to learn.
 
-To run TYPEMILL on a **live** system, simply upload the files to your server.
+To run TYPEMILL on a **live** system, simply upload the files to your server
+
+## Make Folders Writable.
+
+ Make sure that the following folders and all their content are writable (permission 774 recursively):
+
+* cache
+* content
+* media
+* settings
+
+You can use your ftp-software for that.
 
 ## Setup
 

+ 57 - 1
system/Controllers/ContentApiController.php

@@ -6,6 +6,7 @@ use Slim\Http\Request;
 use Slim\Http\Response;
 use Typemill\Models\Folder;
 use Typemill\Models\Write;
+use Typemill\Models\ProcessImage;
 use Typemill\Extensions\ParsedownExtension;
 
 class ContentApiController extends ContentController
@@ -755,6 +756,7 @@ class ContentApiController extends ContentController
 		/* get params from call */
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri();
+		$errors			= false;
 		
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
@@ -792,6 +794,25 @@ class ContentApiController extends ContentController
 		# check if id exists
 		if(!isset($this->content[$this->params['block_id']])){ return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404); }
 
+		# check if block is image
+		$contentBlock 		= $this->content[$this->params['block_id']];
+		$contentBlockStart 	= substr($contentBlock, 0, 2);
+		if($contentBlockStart == '[!' OR $contentBlockStart == '![')
+		{
+			# extract image path
+			preg_match("/\((.*?)\)/",$contentBlock,$matches);
+			if(isset($matches[1]))
+			{
+				$imageBaseName	= explode('-', $matches[1]);
+				$imageBaseName	= str_replace('media/live/', '', $imageBaseName[0]);
+				$processImage 	= new ProcessImage();
+				if(!$processImage->deleteImage($imageBaseName))
+				{
+					$errors = 'Could not delete some of the images, please check manually';
+				}
+			}
+		}
+		
 		# delete the block
 		unset($this->content[$this->params['block_id']]);
 		$this->content = array_values($this->content);
@@ -821,6 +842,41 @@ class ContentApiController extends ContentController
 			return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
 		}
 				
-		return $response->withJson(array('markdown' => $pageMarkdown, 'errors' => false));
+		return $response->withJson(array('markdown' => $pageMarkdown, 'errors' => $errors));
+	}
+
+	public function createImage(Request $request, Response $response, $args)
+	{
+		/* get params from call */
+		$this->params 	= $request->getParams();
+		$this->uri 		= $request->getUri();
+		
+		$imageProcessor	= new ProcessImage();
+		
+		if($imageProcessor->createImage($this->params['image'], $this->settings['images'], $name = false))
+		{
+			return $response->withJson(array('errors' => false));		
+		}
+
+		return $response->withJson(array('errors' => 'could not store image to temporary folder'));	
+	}
+	
+	public function publishImage(Request $request, Response $response, $args)
+	{
+		$params 		= $request->getParsedBody();
+						
+		$imageProcessor	= new ProcessImage();
+		
+		$imageUrl 		= $imageProcessor->publishImage($this->settings['images'], $name = false);
+		if($imageUrl)
+		{
+			$params['markdown']	= str_replace('imgplchldr', $imageUrl, $params['markdown']);
+						
+			$request 	= $request->withParsedBody($params);
+			
+			return $this->updateBlock($request, $response, $args);
+		}
+
+		return $response->withJson(array('errors' => 'could not store image to temporary folder'));	
 	}
 }

+ 11 - 6
system/Controllers/ContentBackendController.php

@@ -23,7 +23,7 @@ class ContentBackendController extends ContentController
 		# get params from call
 		$this->uri 		= $request->getUri();
 		$this->params	= isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
-		
+				
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
 		
@@ -71,7 +71,7 @@ class ContentBackendController extends ContentController
 				$content = trim($contentParts[1]);
 			}
 		}
-
+		
 		return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
 	}
 	
@@ -85,6 +85,7 @@ class ContentBackendController extends ContentController
 	
 	public function showBlox(Request $request, Response $response, $args)
 	{
+		
 		# get params from call
 		$this->uri 		= $request->getUri();
 		$this->params	= isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
@@ -100,7 +101,7 @@ class ContentBackendController extends ContentController
 		
 		# set the status for published and drafted
 		$this->setPublishStatus();
-
+		
 		# set path
 		$this->setItemPath($this->item->fileType);
 
@@ -133,12 +134,16 @@ class ContentBackendController extends ContentController
 			$contentArray 	= $parsedown->text($block);
 
 			/* parse markdown-content-array to content-string */
-			$content[$key]	= $parsedown->markup($contentArray);			
+			$content[$key]	= $parsedown->markup($contentArray);
 		}
 
 		# extract title and delete from content array, array will start at 1 after that.
-		$title = $content[0];
-		unset($content[0]);
+		$title = '# add title';
+		if(isset($content[0]))
+		{
+			$title = $content[0];
+			unset($content[0]);			
+		}
 
 		return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
 	}

+ 7 - 1
system/Controllers/SettingsController.php

@@ -95,7 +95,7 @@ class SettingsController extends Controller
 				/* store them as default theme data with author, year, default settings and field-definitions */
 				$themedata[$themeName] = $themeSettings;
 			}
-			
+						
 			if(isset($themeSettings['forms']['fields']))
 			{
 				$fields = $this->getFields($userSettings, 'themes', $themeName, $themeSettings);
@@ -271,6 +271,12 @@ class SettingsController extends Controller
 			$themeName		= isset($params['theme']) ? $params['theme'] : false;
 			$userInput		= isset($params[$themeName]) ? $params[$themeName] : false;
 			$validate		= new Validation();
+			$themeSettings 	= \Typemill\Settings::getObjectSettings('themes', $themeName);
+			
+			if(isset($themeSettings['settings']['images']))
+			{	
+				$userSettings 	= ['images' => $themeSettings['settings']['images']];
+			}
 			
 			/* set theme name and delete theme settings from user settings for the case, that the new theme has no settings */
 			$userSettings['theme'] = $themeName;

+ 2 - 2
system/Extensions/ParsedownExtension.php

@@ -333,12 +333,12 @@ class ParsedownExtension extends \ParsedownExtra
 			}
 			
 			$block = trim($block, "\n");
-			
+						
 			$cleanBlocks[] = $block;
 		}
 		return $cleanBlocks;
 	}
-
+	
 	public function arrayBlocksToMarkdown(array $arrayBlocks)
 	{	
 		$markdown = '';

+ 325 - 0
system/Models/ProcessImage.php

@@ -0,0 +1,325 @@
+<?php
+namespace Typemill\Models;
+
+class ProcessImage
+{
+	public function createImage(string $image, array $desiredSizes, $name)
+	{
+		# fix error from jpeg-library
+		ini_set ('gd.jpeg_ignore_warning', 1);
+		error_reporting(E_ALL & ~E_NOTICE);
+		
+		# clear temporary folder
+		$this->clearTempFolder();
+		
+		# decode the image from base64-string
+		$imageDecoded	= $this->decodeImage($image);
+		$imageData		= $imageDecoded["image"];
+		$imageType		= $imageDecoded["type"];
+		
+		# transform image-stream into image
+		$image 			= imagecreatefromstring($imageData);
+		
+		# get the size of the original image
+		$imageSize 		= $this->getImageSize($image);
+		
+		# check the desired sizes and calculate the height, if not set
+		$desiredSizes	= $this->setHeight($imageSize, $desiredSizes);
+		
+		# resize the images
+		$resizedImages	= $this->imageResize($image, $imageSize, $desiredSizes, $imageType);
+		
+		$basePath		= getcwd() . DIRECTORY_SEPARATOR . 'media';
+		$tmpFolder		= $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+
+		$this->saveOriginal($tmpFolder, $imageData, 'original', $imageType);
+	
+		if($imageType == "gif" && $this->detectAnimatedGif($imageData))
+		{
+			$this->saveOriginal($tmpFolder, $imageData, 'live', $imageType);
+			
+			return true;
+		}
+		
+		# temporary store resized images
+		foreach($resizedImages as $key => $resizedImage)
+		{
+			$this->saveImage($tmpFolder, $resizedImage, $key, $imageType);
+		}
+		
+		return true;
+	}
+	
+	public function detectAnimatedGif($image_file_contents)
+	{
+		$is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $image_file_contents);
+		if ($is_animated == 1)
+		{
+			return true;
+		}
+		return false;
+	}
+	
+	public function publishImage(array $desiredSizes)
+	{
+		/* get images from tmp folder */
+		$basePath		= getcwd() . DIRECTORY_SEPARATOR . 'media';
+		$tmpFolder		= $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+		$originalFolder	= $basePath . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR;
+		$liveFolder		= $basePath . DIRECTORY_SEPARATOR . 'live' . DIRECTORY_SEPARATOR;
+		
+		$name 			= uniqid();
+		
+		$files 			= scandir($tmpFolder);
+		$success		= true;
+		
+		foreach($files as $file)
+		{
+			if (!in_array($file, array(".","..")))
+			{			
+				$tmpfilename 	= explode(".", $file);
+				
+				if($tmpfilename[0] == 'original')
+				{
+					$success = rename($tmpFolder . $file, $originalFolder . $name . '-' . $file);
+				}
+				else
+				{
+					$success = rename($tmpFolder . $file, $liveFolder . $name . '-' . $file);
+				}
+			}
+		}
+		
+		if($success)
+		{
+			return 'media/live/' . $name . '-live.' . $tmpfilename[1];
+		}
+		
+		return false;
+	}
+	
+	public function decodeImage(string $image)
+	{
+        $imageParts 	= explode(";base64,", $image);
+        $imageType		= explode("/", $imageParts[0]);
+		$imageData		= base64_decode($imageParts[1]);
+	
+		if ($imageData !== false)
+		{
+			return array("image" => $imageData, "type" => $imageType[1]);
+		}
+		
+		return false;
+	}
+
+	public function getImageSize($image)
+	{
+		$width = imagesx($image);
+		$height = imagesy($image);
+		return array('width' => $width, 'height' => $height);
+	}
+	
+	public function setHeight(array $imageSize, array $desiredSizes)
+	{
+		foreach($desiredSizes as $key => $desiredSize)
+		{
+			if($desiredSize['width'] > $imageSize['width'])
+			{
+				$desiredSizes[$key] = $imageSize;
+				continue;
+			}
+			
+			if(!isset($desiredSize['height']))
+			{
+				$resizeFactor					= $imageSize['width'] / $desiredSize['width'];
+				$desiredSizes[$key]['height']	= round( ($imageSize['height'] / $resizeFactor), 0);
+			}
+		}
+		return $desiredSizes;
+	}
+
+	public function imageResize($imageData, array $imageSize, array $desiredSizes, $imageType)
+	{
+		$copiedImages			= array();
+		$source_aspect_ratio 	= $imageSize['width'] / $imageSize['height'];
+
+		#check transparency
+		$transparent_index		= false;
+
+		if ($imageType = ("png" || "gif"))
+		{
+			$transparent_index 	= imagecolortransparent($imageData);
+		}
+				
+		foreach($desiredSizes as $key => $desiredSize)
+		{
+			$desired_aspect_ratio 	= $desiredSize['width'] / $desiredSize['height'];
+
+			if ( $source_aspect_ratio > $desired_aspect_ratio )
+			{
+				# when source image is wider
+				$temp_height 	= $desiredSize['height'];
+				$temp_width 	= ( int ) ($desiredSize['height'] * $source_aspect_ratio);
+				$temp_width 	= round($temp_width, 0);
+			}
+			else
+			{
+				# when source image is similar or taller
+				$temp_width 	= $desiredSize['width'];
+				$temp_height 	= ( int ) ($desiredSize['width'] / $source_aspect_ratio);
+				$temp_height 	= round($temp_height, 0);
+			}
+			
+			# Create a temporary GD image with desired size
+			$temp_gdim = imagecreatetruecolor( $temp_width, $temp_height );
+
+			if ($transparent_index >= 0 && $imageType == "gif")
+			{  	
+				imagepalettecopy($imageData, $temp_gdim);
+				imagefill($temp_gdim, 0, 0, $transparent_index);
+				imagecolortransparent($temp_gdim, $transparent_index);
+				imagetruecolortopalette($temp_gdim, true, 256);
+			}
+			elseif($transparent_index >= 0 && $imageType == "png")
+			{ 
+				imagealphablending($temp_gdim, false);
+				imagesavealpha($temp_gdim,true);
+				$transparent = imagecolorallocatealpha($temp_gdim, 255, 255, 255, 127);
+				imagefilledrectangle($temp_gdim, 0, 0, $temp_width, $temp_height, $transparent);
+			}
+			
+			imagecopyresampled(
+				$temp_gdim,
+				$imageData,
+				0, 0,
+				0, 0,
+				$temp_width, $temp_height,
+				$imageSize['width'], $imageSize['height']
+			);
+
+			# Copy cropped region from temporary image into the desired GD image
+			$x0 = ( $temp_width - $desiredSize['width'] ) / 2;
+			$y0 = ( $temp_height - $desiredSize['height'] ) / 2;
+
+			$desired_gdim = imagecreatetruecolor( $desiredSize['width'], $desiredSize['height'] );
+
+			if ($transparent_index >= 0 && $imageType == "gif")
+			{  	
+				# GIF
+				imagepalettecopy($imageData, $desired_gdim);
+				imagefill($desired_gdim, 0, 0, $transparent_index);
+				imagecolortransparent($desired_gdim, $transparent_index);
+				imagetruecolortopalette($desired_gdim, true, 256);
+			}
+			if ($transparent_index >= 0 && $imageType == "png")
+			{ 
+				imagealphablending($desired_gdim, false);
+				imagesavealpha($desired_gdim,true);
+				$transparent = imagecolorallocatealpha($desired_gdim, 255, 255, 255, 127);
+				imagefilledrectangle($desired_gdim, 0, 0, $desiredSize['width'], $desiredSize['height'], $transparent);
+			}
+			
+			imagecopy(
+				$desired_gdim,
+				$temp_gdim,
+				0, 0,
+				$x0, $y0,
+				$desiredSize['width'], $desiredSize['height']				
+			);
+			$copiedImages[$key]		= $desired_gdim;
+		}
+		return $copiedImages;
+	}
+	
+	public function saveOriginal($folder, $image, $name, $type)
+	{				
+		if(!file_exists($folder))
+		{
+			mkdir($folder, 0774, true);
+		}
+		
+		$path = $folder . $name . '.' . $type;
+		
+		file_put_contents($path, $image);
+	}
+
+	public function saveImage($folder, $image, $name, $type)
+	{
+		if(!file_exists($folder))
+		{
+			mkdir($folder, 0774, true);
+		}
+		
+		if($type == "png")
+		{
+			$result = imagepng( $image, $folder . '/' . $name . '.png' );
+		}
+		elseif($type == "gif")
+		{
+			$result = imagegif( $image, $folder . '/' . $name . '.gif' );
+		}
+		else
+		{
+			$result = imagejpeg( $image, $folder . '/' . $name . '.jpeg' );
+			$type = 'jpeg';
+		}
+		
+		imagedestroy($image);
+		
+		if($result)
+		{
+			return $name . '.' . $type;
+		}
+		
+		return false;
+	}
+	
+	public function clearTempFolder()
+	{
+		$folder		= getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+		$files 		= scandir($folder);
+		$result		= true;
+		
+		foreach($files as $file)
+		{
+			if (!in_array($file, array(".","..")))
+			{			
+				$filelink = $folder . $file;
+				if(!unlink($filelink))
+				{
+					$success = false;
+				}	
+			}
+		}
+		
+		return $result;
+	}
+	
+	public function deleteImage($name)
+	{
+		$baseFolder		= getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR;
+		$original		= $baseFolder . 'original' . DIRECTORY_SEPARATOR . $name . '*';
+		$live			= $baseFolder . 'live' . DIRECTORY_SEPARATOR . $name . '*';
+		$success 		= true;
+		
+		foreach(glob($original) as $image)
+		{
+			if(!unlink($image))
+			{
+				$success = false;
+			}
+		}
+		
+		foreach(glob($live) as $image)
+		{
+			if(!unlink($image))
+			{
+				$success = false;
+			}
+		}
+		
+		return $success;
+	}
+}
+
+?>

+ 4 - 1
system/Routes/Api.php

@@ -18,4 +18,7 @@ $app->post('/api/v1/basefolder', ContentApiController::class . ':createBaseFolde
 
 $app->put('/api/v1/block', ContentApiController::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));
 $app->delete('/api/v1/block', ContentApiController::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router']));
-$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->put('/api/v1/image', ContentApiController::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));

+ 10 - 8
system/Settings.php

@@ -11,12 +11,13 @@ class Settings
 		
 		if($userSettings)
 		{
-			$settings = array_merge($settings, $userSettings);
-			$settings['setup'] = false;
+			$imgSettings 		= $settings['images'];
+			$settings 			= array_merge($settings, $userSettings);
+			$settings['setup'] 	= false;
 		}
-		
-		$settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
-		
+		$settings['images']		= isset($userSettings['images']) ? array_merge($imgSettings, $userSettings['images']) : $imgSettings;
+		$settings['themePath'] 	= $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
+
 		return array('settings' => $settings);
 	}
 	
@@ -44,9 +45,10 @@ class Settings
 			'contentFolder'							=> 'content',
 			'cache'									=> true,
 			'cachePath'								=> $rootPath . 'cache',
-			'version'								=> '1.2.7',
+			'version'								=> '1.2.8',
 			'setup'									=> true,
-			'welcome'								=> true
+			'welcome'								=> true,
+			'images'								=> ['live' => ['width' => 720], 'mlibrary' => ['width' => 50, 'height' => 50]],
 		];
 	}
 	
@@ -87,7 +89,7 @@ class Settings
 			
 			$yaml 		= new Models\WriteYaml();
 			$settings 	= array_merge($userSettings, $settings);
-
+			
 			/* write settings to yaml */
 			$yaml->updateYaml('settings', 'settings.yaml', $settings);					
 		}

+ 4 - 3
system/author/auth/login.twig

@@ -33,9 +33,10 @@
 					<div class="forgotpw"><a href="https://typemill.net/writers/forgot-password" target="_blank">Forgot password?</a></div>
 				{% endif %}			
 			</div>
-
-		</form>
+		</form>		
 		
 	</div>
-	
+	<footer>
+		<a href="{{ base_url() }}">back to startpage</a>
+	</footer>
 {% endblock %}

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

@@ -1,6 +1,15 @@
 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
 
    Copyright (C) 2016 by Dave Gandy

+ 22 - 34
system/author/css/fontello/config.json

@@ -7,45 +7,45 @@
   "ascent": 850,
   "glyphs": [
     {
-      "uid": "f9cbf7508cd04145ade2800169959eef",
-      "css": "font",
+      "uid": "c709da589c923ba3c2ad48d9fc563e93",
+      "css": "cancel",
       "code": 59392,
-      "src": "fontawesome"
+      "src": "entypo"
     },
     {
-      "uid": "e99461abfef3923546da8d745372c995",
-      "css": "cog",
+      "uid": "381da2c2f7fd51f8de877c044d7f439d",
+      "css": "picture",
       "code": 59393,
       "src": "fontawesome"
     },
     {
-      "uid": "381da2c2f7fd51f8de877c044d7f439d",
-      "css": "picture",
+      "uid": "f9cbf7508cd04145ade2800169959eef",
+      "css": "font",
       "code": 59394,
       "src": "fontawesome"
     },
     {
-      "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
-      "css": "off",
+      "uid": "e99461abfef3923546da8d745372c995",
+      "css": "cog",
       "code": 59395,
       "src": "fontawesome"
     },
     {
-      "uid": "d7271d490b71df4311e32cdacae8b331",
-      "css": "home",
+      "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
+      "css": "off",
       "code": 59396,
       "src": "fontawesome"
     },
     {
-      "uid": "44e04715aecbca7f266a17d5a7863c68",
-      "css": "plus",
+      "uid": "d7271d490b71df4311e32cdacae8b331",
+      "css": "home",
       "code": 59397,
       "src": "fontawesome"
     },
     {
-      "uid": "e15f0d620a7897e2035c18c80142f6d9",
-      "css": "link-ext",
-      "code": 61582,
+      "uid": "44e04715aecbca7f266a17d5a7863c68",
+      "css": "plus",
+      "code": 59398,
       "src": "fontawesome"
     },
     {
@@ -54,6 +54,12 @@
       "code": 61618,
       "src": "fontawesome"
     },
+    {
+      "uid": "e15f0d620a7897e2035c18c80142f6d9",
+      "css": "link-ext",
+      "code": 61582,
+      "src": "fontawesome"
+    },
     {
       "uid": "5408be43f7c42bccee419c6be53fdef5",
       "css": "doc-text",
@@ -65,24 +71,6 @@
       "css": "folder-empty",
       "code": 61716,
       "src": "fontawesome"
-    },
-    {
-      "uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
-      "css": "resize-vertical",
-      "code": 59398,
-      "src": "fontawesome"
-    },
-    {
-      "uid": "6605ee6441bf499ffa3c63d3c7409471",
-      "css": "move",
-      "code": 61511,
-      "src": "fontawesome"
-    },
-    {
-      "uid": "5211af474d3a9848f67f945e2ccaf143",
-      "css": "cancel",
-      "code": 59399,
-      "src": "fontawesome"
     }
   ]
 }

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

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

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


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

@@ -1,13 +1,11 @@
 
-.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
-.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&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-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-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }

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

@@ -10,15 +10,13 @@
   /* font-size: 120%; */
 }
  
-.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
-.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
-.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
-.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
-.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
-.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
-.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&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-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-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }

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

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

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

@@ -229,11 +229,11 @@ body {
 }
 @font-face {
       font-family: 'fontello';
-      src: url('./font/fontello.eot?63828600');
-      src: url('./font/fontello.eot?63828600#iefix') format('embedded-opentype'),
-           url('./font/fontello.woff?63828600') format('woff'),
-           url('./font/fontello.ttf?63828600') format('truetype'),
-           url('./font/fontello.svg?63828600#fontello') format('svg');
+      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');
       font-weight: normal;
       font-style: normal;
     }
@@ -298,24 +298,20 @@ body {
     </div>
     <div class="container" id="icons">
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-font">&#xe800;</i> <span class="i-name">icon-font</span><span class="i-code">0xe800</span></div>
-        <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-cog">&#xe801;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe801</span></div>
-        <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-picture">&#xe802;</i> <span class="i-name">icon-picture</span><span class="i-code">0xe802</span></div>
-        <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-off">&#xe803;</i> <span class="i-name">icon-off</span><span class="i-code">0xe803</span></div>
+        <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: 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>
       <div class="row">
-        <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-home">&#xe804;</i> <span class="i-name">icon-home</span><span class="i-code">0xe804</span></div>
-        <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-plus">&#xe805;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe805</span></div>
-        <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-resize-vertical">&#xe806;</i> <span class="i-name">icon-resize-vertical</span><span class="i-code">0xe806</span></div>
-        <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-cancel">&#xe807;</i> <span class="i-name">icon-cancel</span><span class="i-code">0xe807</span></div>
+        <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>
       <div class="row">
-        <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: 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>
-      <div class="row">
         <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


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

@@ -6,23 +6,19 @@
 <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" />
 <missing-glyph horiz-adv-x="1000" />
-<glyph glyph-name="font" unicode="&#xe800;" 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="&#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="cog" unicode="&#xe801;" 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="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="&#xe802;" 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="off" unicode="&#xe803;" 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="&#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="home" unicode="&#xe804;" 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="&#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="plus" unicode="&#xe805;" 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="&#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="resize-vertical" unicode="&#xe806;" d="M393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-10 10-10 25t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
-
-<glyph glyph-name="cancel" unicode="&#xe807;" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
-
-<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="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="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


+ 101 - 21
system/author/css/style.css

@@ -345,6 +345,10 @@ h1 .version-number{
 	font-size: 0.5em;
 	font-weight: 300;
 }
+footer{
+	text-align: center;
+	padding: 20px 0;
+}
 
 /********************
 *  	SETUP FORM 	    *
@@ -1250,6 +1254,7 @@ label .help, .label .help{
 	max-width: 900px;
 	background: #fff;
 	box-shadow: 0 0 2px #ddd;
+	z-index: 99;
 }
 .buttonset .message.error{
 	position: absolute;
@@ -1352,47 +1357,59 @@ label .help, .label .help{
 .blox-editor{
 	position: relative;
 }
-.blox-editor:hover{
+.blox:hover{
 	background: #f9f8f6;
 }
 .blox-buttons{
-	background: #fff;
-}
-.blox-editor textarea{
-	font-family: arial;
-	line-height: 1.5em;
-	font-size: 16px;
-	padding-left: 20px;
-	padding-right: 20px;
-	box-sizing: border-blox;
+	position: absolute;
+    bottom: -15px;
+    left: 15px;
+    z-index: 9;
+	left: 15px;
+    width: 200px;
+	z-index: 99;
 }
 .blox-editor button{
 	display: inline-block;
 	box-sizing: border-box;
-	margin: 2px 2px 10px 0;
-	padding: 6px 12px;
+	margin: 1px;
+	padding: 3px 6px;
+	width: 80px;
+	text-align: center;
 	border-radius: 2px;
 	font-size: 0.9em;
 }
 .blox-editor button.edit{
-	background: #e0474c;
-	color: #f9f8f6;
-	border: 1px solid #e0474c;
+	background: #fff;
+//	background: #f9f8f6;
+	color: #444;
+	border: 1px solid #bbb;
 }
 .blox-editor button.edit:hover{
 	background: #cc4146;
+	color: #eee;
 	border: 1px solid #cc4146;
 }
 .blox-editor button.cancel{
+//	background: #f9f8f6;
 	background: #fff;
-	border: 1px solid #eee;
 	color: #444;	
+	border: 1px solid #bbb;
 }
 .blox-editor button.cancel:hover{
 	background: #e0474c;
 	border: 1px solid #e0474c;
 	color: #eee;
 }
+.blox-editor textarea{
+	font-family: arial;
+	line-height: 1.5em;
+	font-size: 16px;
+	padding-left: 20px;
+	padding-right: 20px;
+	box-sizing: border-blox;
+	min-height: 40px;
+}
 .blox-editor .sideaction{
 	position: absolute;
 	right: -22px;
@@ -1432,6 +1449,14 @@ label .help, .label .help{
 .hidden{
 	display: none;
 }
+.hidden .blox:hover{
+	background: #fff;
+}
+.component{
+	position: relative;
+	width: 100%;
+	z-index:9;
+}
 .fade-editor-enter-active{
   transition: opacity .5s;
 }
@@ -1442,6 +1467,11 @@ label .help, .label .help{
 	padding: 20px;
 	width:100%;
 }
+.format-bar .editactive{
+	position: relative;
+	margin-left: -20px;
+	margin-right: 20px;
+}
 .format-bar .blox-editor{
 	display: inline;
 }
@@ -1565,8 +1595,47 @@ label .help, .label .help{
 .blox blockquote p{
 	margin-left: 50px;
 }
-.blox img{
+.dropbox{
+	min-height: 50px;
+	position: relative;
+	background: #f9f8f6;
+	padding: 20px;
+	box-sizing: border-box;
+	margin-bottom: 10px;
+}
+.dropbox p{
+	cursor: pointer;	
+	border: 2px dashed grey;
+	line-height: 50px;
 	width: 100%;
+	text-align: center;
+	box-sizing:border-box;
+	padding: 0;
+}
+.dropbox input{
+	background: #fff;
+	width: 80%;
+	margin: 2px 0;
+	display: inline-block;
+}
+.dropbox label{
+	width: 20%;
+	display: inline-block;
+}
+.dropbox .imgmeta{
+	margin-top: 20px;
+}
+.input-file{
+    opacity: 0;
+    width: 100%;
+    height: 50px;
+    position: absolute;
+    cursor: pointer;
+}
+.blox img, img.uploadPreview{
+	display: block;
+	margin: auto;
+	max-width: 100%;
 }
 sup{}
 cite{}
@@ -1603,11 +1672,14 @@ hr{
 	font-weight: 400;
 	padding-left: 25px;	
 }
-.blox a, .blox a:link, .blox a:visited{ 
+.blox a, .blox a:link, .blox a:visited,
+footer a, footer a:link, footer a:visited
+{ 
 	text-decoration: none; 
 	color: #e0474c; 
 }
-.blox a:focus, .blox a:hover, .blox a:active{ 
+.blox a:focus, .blox a:hover, .blox a:active,
+footer a:focus, footer a:hover, footer a:active{ 
 	text-decoration: underline;
 }
 .blox .TOC li:before{ color: #bbb; }
@@ -1640,8 +1712,16 @@ hr{
 		font-size: 0.9em;
 	}
 	
+	.blox-editor .loadwrapper{
+		display: block;
+		position: relative;
+		width: 40px;
+		height: 40px;
+		margin: auto;
+		text-align: center;
+	}
 	/* load design editor button */
-	.editor button.load:after,
+	.editor button.load:after, .blox-editor .load:after,
 	.editor button.success:after,
 	.editor button.fail:after
 	{
@@ -1653,7 +1733,7 @@ hr{
 		border-radius: 50%;
 		content: '';
 	}
-	.editor button.load:after{
+	.editor button.load:after, .blox-editor .load:after{
 		border: 8px solid #fff;
 		border-top: 8px solid #ccc;
 		background: #ccc;

+ 5 - 4
system/author/editor/editor-blox.twig

@@ -10,24 +10,25 @@
 			<div class="blox-body">
 			
 				<content-block class="title" :body="false">
-					<div class="blox title" @click="setData( $event, 'text-markdown')" data-id="0" id="blox-0">{{ title }}</div>
+					<div class="blox title" @click.prevent="setData( $event )" data-id="0" id="blox-0">{{ title }}</div>
 				</content-block>
 
 				<div id="sortblox">
 				
 					{% for id, block in content %}
 						<content-block :body="true">
-							<div class="blox" @click="setData( $event, 'textarea-markdown' )" data-id="{{ id }}" id="blox-{{id}}">{{block}}</div>
+							<div class="blox" @click.prevent="setData( $event )" data-id="{{ id }}" id="blox-{{id}}">{{block}}</div>
 						</content-block>
 					{% endfor %}
 
-					<content-block :body="true" v-for="newBlock in newBlocks"><div class="blox" @click="setData( $event, 'textarea-markdown' )" :data-id="newBlock.id" :id="newBlock.blockId" v-html="newBlock.content"></div></content-block>
+					<content-block :body="true" v-for="newBlock in newBlocks"><div class="blox" @click.prevent="setData( $event )" :data-id="newBlock.id" :id="newBlock.blockId" v-html="newBlock.content"></div></content-block>
 				
 				</div>
 				
 				<div class="format-bar">
 					<content-block :body="false">
-						<button class="format-item" @click="setData( $event, 'textarea-markdown' )" 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>
 					</content-block>
 				</div>
 				

+ 15 - 0
system/author/js/author.js

@@ -107,6 +107,21 @@
 		toggleElement.classList.toggle(myclass);
 	}
 	
+	if (window.Element && !Element.prototype.closest) {
+		Element.prototype.closest =
+		function(s) {
+			var matches = (this.document || this.ownerDocument).querySelectorAll(s),
+				i,
+				el = this;
+			do {
+				i = matches.length;
+				while (--i >= 0 && matches.item(i) !== el) {};
+			} while ((i < 0) && (el = el.parentElement));
+			return el;
+		};
+	}
+	
+	
 	/**********************************
 	** 		START VERSION CHECK	 	 **
 	**********************************/

+ 371 - 154
system/author/js/vue-blox.js

@@ -1,5 +1,8 @@
+const eventBus = new Vue();
+
 const contentComponent = Vue.component('content-block', {
 	props: ['body'],
+	template: '<div ref="bloxcomponent" class="blox-editor"><div :class="{ editactive: edit }"><div @keyup.enter="submitBlock" @click="getData"><div class="component"><transition name="fade-editor"><component :disabled="disabled" :compmarkdown="compmarkdown" @updatedMarkdown="compmarkdown = $event" :is="componentType"></component></transition><div class="blox-buttons" v-if="edit"><button class="edit" :disabled="disabled" @click.prevent="saveBlock">save</button><button class="cancel" :disabled="disabled" @click.prevent="switchToPreviewMode">cancel</button></div></div><div :class="preview"><slot></slot></div></div><div class="sideaction" v-if="body"><button class="delete" :disabled="disabled" title="delete content-block" @click.prevent="deleteBlock($event)"><i class="icon-cancel"></i></button></div></div></div>',	
 	data: function () {
 		return {
 			preview: 'visible',
@@ -9,156 +12,168 @@ const contentComponent = Vue.component('content-block', {
 			disabled: false,
 		}
 	},
+	mounted: function()
+	{
+		eventBus.$on('closeComponents', this.closeComponents);
+	},
 	methods: {
-		getData: function()
+		switchToEditMode: function()
 		{
+			eventBus.$emit('closeComponents');
 			self = this;
-			
-			if(self.$root.$data.freeze == false && self.$root.$data.blockType != '')
-			{
-				self.$root.$data.freeze = true;
-				this.preview = 'hidden';
-				this.edit = true;
-				this.compmarkdown = self.$root.$data.blockMarkdown;
-				this.componentType = self.$root.$data.blockType;
-				self.$root.sortable.option("disabled",true);
-			}
-			/*
-				window.addEventListener('click', function(e)
-				{
-					if (!e.target.closest('.editactive'))
-					{
-						console.info('not found');
-						publishController.errors.message = false;
-						
-						this.preview = 'visible';
-						this.edit = false;
-						this.compmarkdown = '';
-						self.componentType = false;
-						self.$root.$data.freeze = false;
-						self.$root.sortable.option("disabled",false);
-					}
-				});
-			*/
+			self.$root.$data.freeze = true; 						/* freeze the data */
+			self.$root.sortable.option("disabled",true);			/* disable sorting */
+			this.preview = 'hidden'; 								/* hide the html-preview */
+			this.edit = true;										/* show the edit-mode */
+			this.compmarkdown = self.$root.$data.blockMarkdown;		/* get markdown data */
+			this.componentType = self.$root.$data.blockType;		/* get block-type of element */
 		},
-		cancelBlock: function()
+		closeComponents: function()
 		{
-			publishController.errors.message = false;
-			
 			this.preview = 'visible';
 			this.edit = false;
 			this.componentType = false;
+		},
+		switchToPreviewMode: function()
+		{
 			self = this;
-			self.$root.$data.freeze = false;
-			self.$root.sortable.option("disabled",false);
+			self.$root.$data.freeze = false;						/* activate the data again */
+			self.$root.sortable.option("disabled",false);			/* activate sorting again */
+			this.preview = 'visible';								/* show the html-preview */
+			this.edit = false;										/* hide the edit mode */
+			this.compmarkdown = '';									/* clear markdown content */
+			this.componentType = false;								/* delete the component type */
+			self.$root.$data.blockType = false;
+			self.$root.$data.blockMarkdown = false;
+			self.$root.$data.file = false;
+			publishController.errors.message = false;				/* delete all error messages */
 		},
- 		submitBlock: function(e){
+		freezePage: function()
+		{
+			this.disabled = 'disabled';
+			publishController.errors.message = false;
+			publishController.publishDisabled = true;
+			var self = this;
+			self.$root.$data.freeze = true;
+		},
+		activatePage: function()
+		{
+			this.disabled = false;
+			publishController.publishDisabled = false;
+		},
+		getData: function()
+		{
+			self = this;
+			if(self.$root.$data.blockType != '')
+			{
+				this.switchToEditMode();
+			}
+			
+			/*
+			if(self.$root.$data.freeze == false && self.$root.$data.blockType != '')
+			{
+				this.switchToEditMode();
+			}
+			*/
+		},
+ 		submitBlock: function(){
 			var emptyline = /^\s*$(?:\r\n?|\n)/gm;
 			if(this.compmarkdown.search(emptyline) > -1)
 			{
 				var checkempty = this.compmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"");
 				if(checkempty == '')
 				{
-					publishController.errors.message = false;
-					
-					this.preview = 'visible';
-					this.edit = false;
-					this.componentType = false;
-					self = this;
-					self.$root.$data.freeze = false;
-					self.$root.sortable.option("disabled",false);
+					this.switchToPreviewMode();
 				}
 				else
 				{
-					self.$root.sortable.option("disabled",false);
 					this.saveBlock();
 				}
 			}
-		},		
+		},
 		saveBlock: function()
-		{
-			publishController.errors.message = false;
-			this.disabled = 'disabled';
-			
-			var self = this;
-
-			self.$root.$data.freeze = true;
-			
-			var url = self.$root.$data.root + '/api/v1/block';
-			
-			var params = {
-				'url':				document.getElementById("path").value,
-				'markdown':			this.compmarkdown,
-				'block_id':			self.$root.$data.blockId,
-				'csrf_name': 		document.getElementById("csrf_name").value,
-				'csrf_value':		document.getElementById("csrf_value").value,
-			};
-			
-			var method 	= 'PUT';
-			
-			sendJson(function(response, httpStatus)
+		{			
+			if(this.compmarkdown == undefined || this.compmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '')
 			{
-				if(httpStatus == 400)
+				this.switchToPreviewMode();	
+			}
+			else
+			{
+				this.freezePage();
+				
+				var self = this;
+				
+				if(this.componentType == 'image-component' && self.$root.$data.file)
 				{
-					self.disabled = false;
-					publishController.publishDisabled = false;
+					var url = self.$root.$data.root + '/api/v1/image';
+					var method 	= 'PUT';
 				}
-				if(response)
+				else
 				{
-					self.disabled = false;
-					publishController.publishDisabled = false;
-					
-					var result = JSON.parse(response);
-										
-					if(result.errors)
+					var url = self.$root.$data.root + '/api/v1/block';
+					var method 	= 'PUT';
+				}
+				
+				var params = {
+					'url':				document.getElementById("path").value,
+					'markdown':			this.compmarkdown,
+					'block_id':			self.$root.$data.blockId,
+					'csrf_name': 		document.getElementById("csrf_name").value,
+					'csrf_value':		document.getElementById("csrf_value").value,
+				};
+
+				sendJson(function(response, httpStatus)
+				{
+					if(httpStatus == 400)
 					{
-						console.info(result.errors);
-						publishController.errors.message = result.errors.markdown[0];
-						return false;
+						self.activatePage();
 					}
-					else
+					if(response)
 					{
-						self.preview = 'visible';
-						self.edit = false;
-						self.componentType = false;						
-						self.$root.$data.freeze = false;
+						self.activatePage();
 						
-						if(self.$root.$data.blockId == 99999)
+						var result = JSON.parse(response);
+											
+						if(result.errors)
 						{
-							self.$root.$data.markdown.push(result.markdown);
-							self.$root.$data.newBlocks.push(result);
-							
-							self.$root.$data.blockMarkdown = '';
-							self.$root.$data.blockType = 'textarea-markdown';
-							self.getData();
+							publishController.errors.message = result.errors.markdown[0];
 						}
 						else
 						{
-							var htmlid = "blox-" + self.$root.$data.blockId;
-							var html = document.getElementById(htmlid);
-							html.innerHTML = result.content;
+							self.switchToPreviewMode();
 							
-							self.$root.$data.markdown[self.$root.$data.blockId] = result.markdown;
-
-							self.$root.$data.blockMarkdown = '';
-							self.$root.$data.blockType = '';
+							if(self.$root.$data.blockId == 99999)
+							{
+								self.$root.$data.markdown.push(result.markdown);
+								self.$root.$data.newBlocks.push(result);
+								
+								self.$root.$data.blockMarkdown = '';
+								self.$root.$data.blockType = 'markdown-component';
+								self.getData();
+							}
+							else
+							{
+								var htmlid = "blox-" + self.$root.$data.blockId;
+								var html = document.getElementById(htmlid);
+								html.innerHTML = result.content;
+								
+								self.$root.$data.markdown[self.$root.$data.blockId] = result.markdown;
+								self.$root.$data.blockMarkdown = '';
+								self.$root.$data.blockType = '';
+							}
 						}
-
-						publishController.publishResult = "";
 					}
-				}
-			}, method, url, params);
+				}, method, url, params);
+			}
 		},
 		deleteBlock: function(event)
 		{	
-			var bloxeditor = event.target.parentElement.parentElement;
-			console.info(bloxeditor);
-			var bloxid = bloxeditor.getElementsByClassName('blox')[0].dataset.id;
-			bloxeditor.id = "delete-"+bloxid;
+			this.freezePage();
 			
-			this.disabled = 'disabled';
+			var bloxeditor = event.target.closest('.blox-editor');
 			
-			publishController.errors.message = false;
+			var bloxid = bloxeditor.getElementsByClassName('blox')[0].dataset.id;
+			bloxeditor.firstChild.id = "delete-"+bloxid;
 
 			var self = this;
 			
@@ -177,24 +192,21 @@ const contentComponent = Vue.component('content-block', {
 			{
 				if(httpStatus == 400)
 				{
-					self.disabled = false;
+					self.activatePage();
 				}
 				if(response)
 				{
-					self.disabled = false;
+					self.activatePage();
 					
 					var result = JSON.parse(response);
 
 					if(result.errors)
 					{
 						publishController.errors.message = result.errors;
-						publishController.publishDisabled = false;
-						return false;
 					}
 					else
-					{
-						self.edit = false;
-						self.componentType = false;
+					{	
+						self.switchToPreviewMode();
 						
 						var deleteblock = document.getElementById("delete-"+bloxid);
 						deleteblock.parentElement.remove(deleteblock);
@@ -206,46 +218,22 @@ const contentComponent = Vue.component('content-block', {
 							blox[i].dataset.id = i;
 						}
 
-						self.$root.$data.freeze = false;
-						self.$root.$data.markdown = result.markdown;
+						self.$root.$data.markdown = result.markdown;						
 						self.$root.$data.blockMarkdown = '';
 						self.$root.$data.blockType = '';
-
-						publishController.publishDisabled = false;
-						publishController.publishResult = "";
 					}
 				}
 			}, method, url, params);
 		},
 	},
-	/*
-	mounted: function() {
-		var self = this;
-		
-		self.sortable = new Sortable(sortblox, {
-			animation: 150,
-			onEnd: function (evt) {
-				var params = {
-					'url':			document.getElementById("path").value,
-					'old_index': 	evt.oldIndex,
-					'new_index':	evt.newIndex,
-					'csrf_name': 	document.getElementById("csrf_name").value,
-					'csrf_value':	document.getElementById("csrf_value").value,
-				};
-				self.moveBlock(params);
-			},
-		});
-	},
-	*/
-	template: '<div class="blox-editor"><div :class="{ editactive: edit }"><div @keyup.enter="submitBlock" @click="getData"><transition name="fade-editor"><component :disabled="disabled" :compmarkdown="compmarkdown" @updatedMarkdown="compmarkdown = $event" :is="componentType"></component></transition><div :class="preview"><slot></slot></div></div><div class="blox-buttons" v-if="edit"><button class="edit" :disabled="disabled" @click.prevent="saveBlock">save</button><button class="cancel" :disabled="disabled" @click.prevent="cancelBlock">cancel</button></div><div class="sideaction" v-if="body"><button class="delete" :disabled="disabled" title="delete content-block" @click.prevent="deleteBlock($event)"><i class="icon-cancel"></i></button></div></div></div>',
 })
 
-const textareaComponent = Vue.component('textarea-markdown', {
+const markdownComponent = Vue.component('markdown-component', {
 	props: ['compmarkdown', 'disabled'],
 	template: '<div><textarea ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown"></textarea></div>',
 	mounted: function(){
-		autosize(document.querySelector('textarea'));
 		this.$refs.markdown.focus();
+		autosize(document.querySelectorAll('textarea'));
 	},
 	methods: {
 		updatemarkdown: function(event)
@@ -255,12 +243,12 @@ const textareaComponent = Vue.component('textarea-markdown', {
 	},
 })
 
-const textComponent = Vue.component('text-markdown', {
+const titleComponent = Vue.component('title-component', {
 	props: ['compmarkdown', 'disabled'],
 	template: '<div><input type="text" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown"></div>',
 	mounted: function(){
-		autosize(document.querySelector('textarea'));
 		this.$refs.markdown.focus();
+		autosize(document.querySelectorAll('textarea'));
 	},
 	methods: {
 		updatemarkdown: function(event)
@@ -270,11 +258,212 @@ const textComponent = Vue.component('text-markdown', {
 	},
 })
 
-const imageComponent = Vue.component('input-image', { 
-	template: '<div>Image component</div>',
+const imageComponent = Vue.component('image-component', {
+	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>',
+	data: function(){
+		return {
+			maxsize: 5, // megabyte
+			imgpreview: false,
+			load: false,
+			imgmeta: false,
+			imgalt: '',
+			imgtitle: '',
+			imgcaption: '',
+			imglink: '',
+			imgfile: 'imgplchldr',
+		}
+	},
+	mounted: function(){
+		// autosize(document.querySelector('textarea'));
+		this.$refs.markdown.focus();
+		
+		if(this.compmarkdown)
+		{			
+			this.imgmeta = true;
+			
+			var imgmarkdown = this.compmarkdown;
+						
+			var imgcaption = imgmarkdown.match(/\*.*?\*/);
+			if(imgcaption){
+				this.imgcaption = imgcaption[0].slice(1,-1);
+				imgmarkdown = imgmarkdown.replace(this.imgcaption,'');
+				imgmarkdown = imgmarkdown.replace(/\r?\n|\r/g,'');
+			}
+			
+			if(this.compmarkdown[0] == '[')
+			{
+				var imglink = this.compmarkdown.match(/\(.*?\)/g);
+				if(imglink[1])
+				{
+					this.imglink = imglink[1].slice(1,-1);
+					imgmarkdown = imgmarkdown.replace(imglink[1],'');
+					imgmarkdown = imgmarkdown.slice(1, -1);
+				}
+			}
+
+			var imgtitle = imgmarkdown.match(/\".*?\"/);
+			if(imgtitle)
+			{
+				this.imgtitle = imgtitle[0].slice(1,-1);
+				imgmarkdown = imgmarkdown.replace(imgtitle[0], '');
+			}
+			
+			var imgalt = imgmarkdown.match(/\[.*?\]/);
+			if(imgalt)
+			{
+				this.imgalt = imgalt[0].slice(1,-1);
+			}
+			var imgpreview = imgmarkdown.match(/\(.*?\)/);
+			if(imgpreview)
+			{
+				this.imgpreview = imgpreview[0].slice(1,-1);
+				this.imgfile = this.imgpreview;
+			}
+		}
+	},
+	methods: {
+		updatemarkdown: function(event)
+		{
+			this.$emit('updatedMarkdown', event.target.value);
+		},
+		createmarkdown: function()
+		{
+			errors = false;
+			
+			if(this.imgalt.length < 101)
+			{
+				imgmarkdown = '![' + this.imgalt + ']';
+			}
+			else
+			{
+				errors = 'Maximum size of image alt-text is 100 characters';
+				imgmarkdown = '![]';
+			}
+			
+			if(this.imgtitle != '')
+			{
+				if(this.imgtitle.length < 101)
+				{
+					imgmarkdown = imgmarkdown + '(' + this.imgfile + ' "' + this.imgtitle + '")';
+				}
+				else
+				{
+					errors = 'Maximum size of image title is 100 characters';
+				}
+			}
+			else
+			{
+				imgmarkdown = imgmarkdown + '(' + this.imgfile + ')';		
+			}
+			
+			if(this.imglink != '')
+			{
+				if(this.imglink.length < 101)
+				{
+					imgmarkdown = '[' + imgmarkdown + '](' + this.imglink + ')';
+				}
+				else
+				{
+					errors = 'Maximum size of image link is 100 characters';
+				}
+			}
+			
+			if(this.imgcaption != '')
+			{
+				if(this.imgcaption.length < 140)
+				{
+					imgmarkdown = imgmarkdown + '\n*' + this.imgcaption + '*'; 
+				}
+				else
+				{
+					errors = 'Maximum size of image caption is 140 characters';
+				}
+			}
+			
+			if(errors)
+			{
+				this.$parent.freezePage();
+				publishController.errors.message = errors;
+			}
+			else
+			{
+				publishController.errors.message = false;
+				this.$parent.activatePage();
+				this.$emit('updatedMarkdown', imgmarkdown);
+			}
+		},
+		onFileChange: function( e )
+		{
+			if(e.target.files.length > 0)
+			{
+				let imageFile = e.target.files[0];
+				let size = imageFile.size / 1024 / 1024;
+				
+				if (!imageFile.type.match('image.*'))
+				{
+					publishController.errors.message = "Only images are allowed.";
+				} 
+				else if (size > this.maxsize)
+				{
+					publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB";
+				}
+				else
+				{
+					self = this;					
+					this.$parent.freezePage();
+					this.$root.$data.file = true;
+					this.load = true;
+					
+					let reader = new FileReader();
+					reader.readAsDataURL(imageFile);
+					reader.onload = function(e) {
+						self.imgpreview = e.target.result;
+						self.$emit('updatedMarkdown', '![](imgplchldr)');
+						
+						/* load image to server */
+						var url = self.$root.$data.root + '/api/v1/image';
+						
+						var params = {
+							'url':				document.getElementById("path").value,
+							'image':			e.target.result,
+							'csrf_name': 		document.getElementById("csrf_name").value,
+							'csrf_value':		document.getElementById("csrf_value").value,
+						};
+									
+						var method 	= 'POST';
+						
+						sendJson(function(response, httpStatus)
+						{							
+							if(httpStatus == 400)
+							{
+								self.$parent.activatePage();
+							}
+							if(response)
+							{
+								self.$parent.activatePage();
+								self.load = false;
+								
+								var result = JSON.parse(response);
+
+								if(result.errors)
+								{
+									publishController.errors.message = result.errors;
+								}
+								else
+								{
+									self.imgmeta = true;
+								}
+							}
+						}, method, url, params);						
+					}
+				}
+			}
+		}
+	}
 })
 
-const tableComponent = Vue.component('input-table', { 
+const tableComponent = Vue.component('table', { 
 	template: '<div>table component</div>', 
 })
 
@@ -283,10 +472,9 @@ let editor = new Vue({
 	el: '#blox',
 	components: {
 		'content-component': contentComponent,
-		'textarea-component': textareaComponent,
-		'text-component': textComponent,
-		'image-component': imageComponent,
-		'table-component': tableComponent,
+		'markdown-component': markdownComponent,
+		'title-component': titleComponent,
+		'image-component': imageComponent,		
 	},
 	data: {
 		root: document.getElementById("main").dataset.url,
@@ -294,6 +482,7 @@ let editor = new Vue({
 		blockId: false,
 		blockType: false,
 		blockMarkdown: '',
+		file: false,
 		freeze: false,
 		newBlocks: [],
 		draftDisabled: true,
@@ -346,14 +535,43 @@ let editor = new Vue({
 				};
 				self.moveBlock(params);
 			},
-		});		
+		});
 	},
 	methods: {
 		setData: function(event, blocktype, body)
 		{
 			this.blockId = event.currentTarget.dataset.id;
-			this.blockType = blocktype;
+			/* this.blockType = blocktype; */
 			this.blockMarkdown = this.markdown[this.blockId];
+			if(blocktype)
+			{
+				this.blockType = blocktype;
+			}
+			else if(this.blockId == 0)
+			{ 
+				this.blockType = "title-component"
+			} 
+			else 
+			{
+				this.blockType = this.determineBlockType(this.blockMarkdown);
+			}
+		},
+		determineBlockType: function(block)
+		{
+			var firstChar = block[0];
+			var secondChar = block[1];
+			var thirdChar = block[2];
+			
+			switch(firstChar){
+				case "!":
+					if(secondChar == "[") { return "image-component" }
+					break;
+				case "[":
+					if(secondChar == "!" && thirdChar == "[") { return "image-component" } else { return "markdown-component" }
+					break;
+				default: 
+					return 'markdown-component';
+			}
 		},
 		moveBlock: function(params)
 		{
@@ -362,7 +580,7 @@ let editor = new Vue({
 			var url = this.root + '/api/v1/moveblock';
 		
 			var self = this;			
-						
+			
 			var method 	= 'PUT';
 			
 			sendJson(function(response, httpStatus)
@@ -379,7 +597,6 @@ let editor = new Vue({
 					{
 						publishController.errors.message = result.errors;
 						publishController.publishDisabled = false;
-						return false;
 					}
 					else
 					{

+ 2 - 0
system/author/layouts/layout.twig

@@ -6,6 +6,8 @@
 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
 		<meta name="description" content="Configure your TYPEMILL website"/>
+
+		<base href="{{ base_url }}/">
 		
 		<meta name="msapplication-TileColor" content="#F9F8F6" />
 		<meta name="msapplication-TileImage" content="{{ base_url }}/system/author/img/mstile-144x144.png" />		

+ 2 - 0
system/author/layouts/layoutAuth.twig

@@ -7,6 +7,8 @@
 
 		<meta name="description" content="Welcome to your new TYPEMILL website" />
 		<meta name="robots" content="noindex" />
+
+		<base href="{{ base_url }}/">		
 		
 		<meta name="msapplication-TileColor" content="#F9F8F6" />
 		<meta name="msapplication-TileImage" content="{{ base_url }}/system/author/img/mstile-144x144.png" />		

+ 2 - 0
system/author/layouts/layoutBlank.twig

@@ -7,6 +7,8 @@
 
 		<meta name="description" content="Configure your TYPEMILL website"/>
 		
+		<base href="{{ base_url }}/">
+				
 		<meta name="msapplication-TileColor" content="#F9F8F6" />
 		<meta name="msapplication-TileImage" content="{{ base_url }}/system/author/img/mstile-144x144.png" />		
 		<link rel="icon" type="image/png" href="{{ base_url }}/system/author/img/favicon-32x32.png" sizes="32x32" />

+ 2 - 0
system/author/layouts/layoutBlox.twig

@@ -7,6 +7,8 @@
 
 		<meta name="description" content="Edit your TYPEMILL website" />
 		
+		<base href="{{ base_url }}/">
+		
 		<meta name="msapplication-TileColor" content="#F9F8F6" />
 		<meta name="msapplication-TileImage" content="{{ base_url }}/system/author/img/mstile-144x144.png" />		
 		<link rel="icon" type="image/png" href="{{ base_url }}/system/author/img/favicon-32x32.png" sizes="32x32" />

+ 2 - 0
system/author/layouts/layoutEditor.twig

@@ -7,6 +7,8 @@
 
 		<meta name="description" content="Edit your TYPEMILL website" />
 		
+		<base href="{{ base_url }}/">
+		
 		<meta name="msapplication-TileColor" content="#F9F8F6" />
 		<meta name="msapplication-TileImage" content="{{ base_url }}/system/author/img/mstile-144x144.png" />		
 		<link rel="icon" type="image/png" href="{{ base_url }}/system/author/img/favicon-32x32.png" sizes="32x32" />

+ 2 - 0
themes/typemill/partials/layout.twig

@@ -5,6 +5,8 @@
 		<title>{% block title %}{% endblock %}</title>
 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
+		<base href="{{ base_url }}/">
+		
 		<meta name="description" content="{{ description }}" />
 		<meta name="author" content="{{ settings.author }}" />
 		<meta name="generator" content="TYPEMILL" />

+ 2 - 0
themes/typemill/partials/layoutCover.twig

@@ -5,6 +5,8 @@
 		<title>{% block title %}{% endblock %}</title>
 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
+		<base href="{{ base_url }}/">
+		
 		<meta name="description" content="{{ description }}" />
 		<meta name="author" content="{{ settings.author }}" />
 		<meta name="generator" content="TYPEMILL" />

+ 4 - 1
themes/typemill/typemill.yaml

@@ -1,5 +1,5 @@
 name: Typemill Theme
-version: 1.1.0
+version: 1.1.1
 description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. No JavaScript is used. 
 author: Sebastian Schürmanns
 homepage: https://typemill.net
@@ -12,6 +12,9 @@ settings:
   modifiedText: 'Last updated'
   modifiedFormat: 'd.m.Y'
   authorIntro: 'Author'
+  images:
+    live:
+      width: 820
 
 forms:
   fields:

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