Sfoglia il codice sorgente

Version 1.2.5: Create Pages

Sebastian 6 anni fa
parent
commit
77985ccacb
85 ha cambiato i file con 619 aggiunte e 158 eliminazioni
  1. 1 1
      cache/lastCache.txt
  2. 0 0
      content/00-typemill/00-use-cases.md
  3. 1 1
      content/00-typemill/01-roadmap.md
  4. 0 0
      content/00-typemill/02-features.md
  5. 0 0
      content/01-getting-started/00-system-requirements.md
  6. 0 0
      content/01-getting-started/01-installation.md
  7. 2 1
      content/01-getting-started/02-settings.md
  8. 0 0
      content/01-getting-started/03-update.md
  9. 0 0
      content/01-getting-started/index.md
  10. 0 1
      content/02-for writers/00-quick start.txt
  11. 1 0
      content/02-for-writers/00-quick start.md
  12. 0 0
      content/02-for-writers/01-author panel.md
  13. 0 0
      content/02-for-writers/02-naming files and folders.md
  14. 0 0
      content/02-for-writers/03-folder structure.md
  15. 0 0
      content/02-for-writers/04-mardown.md
  16. 0 0
      content/02-for-writers/05-google sitemap.md
  17. 0 0
      content/02-for-writers/06-themes.md
  18. 0 0
      content/02-for-writers/07-plugins.md
  19. 0 0
      content/02-for-writers/08-forgot password.md
  20. 0 0
      content/02-for-writers/index.md
  21. 0 0
      content/03-for-theme-developers/00-quick start.md
  22. 0 0
      content/03-for-theme-developers/01-theme structure.md
  23. 0 0
      content/03-for-theme-developers/02-theme meta.md
  24. 0 0
      content/03-for-theme-developers/03-asset tags.md
  25. 0 0
      content/03-for-theme-developers/04-twig.md
  26. 0 0
      content/03-for-theme-developers/05-theme variables/00-content.md
  27. 0 0
      content/03-for-theme-developers/05-theme variables/01-title.md
  28. 0 0
      content/03-for-theme-developers/05-theme variables/02-description.md
  29. 0 0
      content/03-for-theme-developers/05-theme variables/03-image.md
  30. 0 0
      content/03-for-theme-developers/05-theme variables/04-base url.md
  31. 0 0
      content/03-for-theme-developers/05-theme variables/05-item.md
  32. 0 0
      content/03-for-theme-developers/05-theme variables/06-breadcrumb.md
  33. 0 0
      content/03-for-theme-developers/05-theme variables/07-navigation.md
  34. 0 0
      content/03-for-theme-developers/05-theme variables/08-settings.md
  35. 0 0
      content/03-for-theme-developers/05-theme variables/index.md
  36. 0 0
      content/03-for-theme-developers/index.md
  37. 0 0
      content/04-for-plugin-developers/00-intro.md
  38. 0 0
      content/04-for-plugin-developers/01-tutorial/00-cookie consent plugin.md
  39. 0 0
      content/04-for-plugin-developers/01-tutorial/01-write the yaml file.md
  40. 0 0
      content/04-for-plugin-developers/01-tutorial/02-write the php file.md
  41. 0 0
      content/04-for-plugin-developers/01-tutorial/03-add methods.md
  42. 0 0
      content/04-for-plugin-developers/01-tutorial/04-create the structure.md
  43. 0 0
      content/04-for-plugin-developers/01-tutorial/05-add twig template.md
  44. 0 0
      content/04-for-plugin-developers/01-tutorial/06-add default values.md
  45. 0 0
      content/04-for-plugin-developers/01-tutorial/07-use variables in twig.md
  46. 0 0
      content/04-for-plugin-developers/01-tutorial/08-make variables editable.md
  47. 0 0
      content/04-for-plugin-developers/01-tutorial/index.md
  48. 0 0
      content/04-for-plugin-developers/02-documentation/00-file structure.md
  49. 0 0
      content/04-for-plugin-developers/02-documentation/01-configuration file.md
  50. 0 0
      content/04-for-plugin-developers/02-documentation/02-field overview.md
  51. 0 0
      content/04-for-plugin-developers/02-documentation/03-basic php file.md
  52. 0 0
      content/04-for-plugin-developers/02-documentation/04-event overview.md
  53. 0 0
      content/04-for-plugin-developers/02-documentation/05-method overview.md
  54. 0 0
      content/04-for-plugin-developers/02-documentation/06-routes.md
  55. 0 0
      content/04-for-plugin-developers/02-documentation/07-middleware.md
  56. 0 0
      content/04-for-plugin-developers/02-documentation/index.md
  57. 0 0
      content/04-for-plugin-developers/index.md
  58. 0 0
      content/05-info/00-release-notes.md
  59. 0 0
      content/05-info/01-usage-and-licence.md
  60. 0 0
      content/05-info/02-Imprint-and-privacy.md
  61. 0 0
      content/05-info/03-markdown-test.md
  62. 203 11
      system/Controllers/ContentApiController.php
  63. 2 2
      system/Controllers/ContentBackendController.php
  64. 60 9
      system/Controllers/ContentController.php
  65. 37 0
      system/Models/Validation.php
  66. 5 56
      system/Models/Write.php
  67. 5 1
      system/Routes/Api.php
  68. 24 24
      system/author/css/fontello/config.json
  69. 2 2
      system/author/css/fontello/css/fontello-codes.css
  70. 3 3
      system/author/css/fontello/css/fontello-embedded.css
  71. 2 2
      system/author/css/fontello/css/fontello-ie7-codes.css
  72. 2 2
      system/author/css/fontello/css/fontello-ie7.css
  73. 9 9
      system/author/css/fontello/css/fontello.css
  74. 7 7
      system/author/css/fontello/demo.html
  75. BIN
      system/author/css/fontello/font/fontello.eot
  76. 2 2
      system/author/css/fontello/font/fontello.svg
  77. BIN
      system/author/css/fontello/font/fontello.ttf
  78. BIN
      system/author/css/fontello/font/fontello.woff
  79. BIN
      system/author/css/fontello/font/fontello.woff2
  80. 64 3
      system/author/css/style.css
  81. 10 0
      system/author/intern404.twig
  82. 2 2
      system/author/js/vue-editor.js
  83. 128 15
      system/author/js/vue-navi.js
  84. 34 0
      system/author/layouts/layoutBlank.twig
  85. 13 4
      system/author/partials/editorNavi.twig

+ 1 - 1
cache/lastCache.txt

@@ -1 +1 @@
-1537594730
+1538860826

+ 0 - 0
content/00-typemill/00-use cases.md → content/00-typemill/00-use-cases.md


+ 1 - 1
content/00-typemill/02-roadmap.md → content/00-typemill/01-roadmap.md

@@ -1,6 +1,6 @@
 # Roadmap
 
-There are a lot of plans for future releases of TYPEMILL, but it also follows the concept of simplicity. To prevent TYPEMILL from becomming a feature soup, it will strictly focus on the writers needs. 
+There are a lot of plans for future releases of TYPEMILL, but it also follows the concept of simplicity. To prevent TYPEMILL from becomming a feature soup, it will strictly focus on the writers needs.
 
 Here are some **milestones** of the past:
 

+ 0 - 0
content/00-typemill/01-features.md → content/00-typemill/02-features.md


+ 0 - 0
content/01-getting started/00-system requirements.md → content/01-getting-started/00-system-requirements.md


+ 0 - 0
content/01-getting started/02-installation.md → content/01-getting-started/01-installation.md


+ 2 - 1
content/01-getting started/03-settings.md → content/01-getting-started/02-settings.md

@@ -1,6 +1,6 @@
 # Settings
 
-As of Version 1.1.3 you can edit all settings in the new author panel of TYPEMILL. Just visit the url `yourwebsite.com/tm/login` and go to settings after the login. There you can edit:
+As of Version 1.1.3 you can edit all settings in the new author panel of TYPEMILL. Just visit the url `yourwebsite.com/tm/login` and go to settings after the login. There you can edit: 
 
 * The system (basic settings).
 * Themes (choose themes and configure it).
@@ -13,6 +13,7 @@ All settings are stored in the `\settings` folder of TYPEMILL. It is not recomme
 
 There are some settings that are not available via the author panel. Most of them are not really useful, but if you are a developer and if you develop a theme or a plugin locally, you probably want to display a detailed error report. To do so, simply add the following line to the settings.yaml: 
 
+
 ````
 displayErrorDetails: true
 ````

+ 0 - 0
content/01-getting started/04-update.md → content/01-getting-started/03-update.md


+ 0 - 0
content/01-getting started/index.md → content/01-getting-started/index.md


+ 0 - 1
content/02-for writers/00-quick start.txt

@@ -1 +0,0 @@
-["# Quick Start for Writers","You are a pro and don't want to read the whole manual? No problem, this is a quick overview:","- **Setup**: Login to your system and setup the system, the themes and the plugins in the author panel.\n- **Content**: Organize your content in folders and markdown files and put them in the `\\content` folder of TYPEMILL. \n- **Markdown**: Use the Markdown syntax for your content files. Markdown Extra (e.g. tables, footnotes) is supported, too.\n- **Naming conventions**: Use prefixes like `01-` or `aa_` to sort your folders and files.\n- **Index files**: Add an `index.md` file to a folder to create content for the folder itself.\n- **F5**: After some changes, use the `F5` key to refresh the navigation manually.\n- **Lean back** and let TYPEMILL create a nice website for you.","\nThe TYPEMILL system ships with this user manual in the content folder. Check how the files are written and how the folders are organized."]

+ 1 - 0
content/02-for writers/00-quick start.md → content/02-for-writers/00-quick start.md

@@ -10,4 +10,5 @@ You are a pro and don't want to read the whole manual? No problem, this is a qui
 - **F5**: After some changes, use the `F5` key to refresh the navigation manually.
 - **Lean back** and let TYPEMILL create a nice website for you.
 
+
 The TYPEMILL system ships with this user manual in the content folder. Check how the files are written and how the folders are organized.

+ 0 - 0
content/02-for writers/01-author panel.md → content/02-for-writers/01-author panel.md


+ 0 - 0
content/02-for writers/02-naming files and folders.md → content/02-for-writers/02-naming files and folders.md


+ 0 - 0
content/02-for writers/03-folder structure.md → content/02-for-writers/03-folder structure.md


+ 0 - 0
content/02-for writers/04-mardown.md → content/02-for-writers/04-mardown.md


+ 0 - 0
content/02-for writers/05-google sitemap.md → content/02-for-writers/05-google sitemap.md


+ 0 - 0
content/02-for writers/06-themes.md → content/02-for-writers/06-themes.md


+ 0 - 0
content/02-for writers/07-plugins.md → content/02-for-writers/07-plugins.md


+ 0 - 0
content/02-for writers/08-forgot password.md → content/02-for-writers/08-forgot password.md


+ 0 - 0
content/02-for writers/index.md → content/02-for-writers/index.md


+ 0 - 0
content/03-for theme developers/00-quick start.md → content/03-for-theme-developers/00-quick start.md


+ 0 - 0
content/03-for theme developers/01-theme structure.md → content/03-for-theme-developers/01-theme structure.md


+ 0 - 0
content/03-for theme developers/02-theme meta.md → content/03-for-theme-developers/02-theme meta.md


+ 0 - 0
content/03-for theme developers/03-asset tags.md → content/03-for-theme-developers/03-asset tags.md


+ 0 - 0
content/03-for theme developers/04-twig.md → content/03-for-theme-developers/04-twig.md


+ 0 - 0
content/03-for theme developers/05-theme variables/00-content.md → content/03-for-theme-developers/05-theme variables/00-content.md


+ 0 - 0
content/03-for theme developers/05-theme variables/01-title.md → content/03-for-theme-developers/05-theme variables/01-title.md


+ 0 - 0
content/03-for theme developers/05-theme variables/02-description.md → content/03-for-theme-developers/05-theme variables/02-description.md


+ 0 - 0
content/03-for theme developers/05-theme variables/03-image.md → content/03-for-theme-developers/05-theme variables/03-image.md


+ 0 - 0
content/03-for theme developers/05-theme variables/04-base url.md → content/03-for-theme-developers/05-theme variables/04-base url.md


+ 0 - 0
content/03-for theme developers/05-theme variables/05-item.md → content/03-for-theme-developers/05-theme variables/05-item.md


+ 0 - 0
content/03-for theme developers/05-theme variables/06-breadcrumb.md → content/03-for-theme-developers/05-theme variables/06-breadcrumb.md


+ 0 - 0
content/03-for theme developers/05-theme variables/07-navigation.md → content/03-for-theme-developers/05-theme variables/07-navigation.md


+ 0 - 0
content/03-for theme developers/05-theme variables/08-settings.md → content/03-for-theme-developers/05-theme variables/08-settings.md


+ 0 - 0
content/03-for theme developers/05-theme variables/index.md → content/03-for-theme-developers/05-theme variables/index.md


+ 0 - 0
content/03-for theme developers/index.md → content/03-for-theme-developers/index.md


+ 0 - 0
content/04-for plugin developers/00-intro.md → content/04-for-plugin-developers/00-intro.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/00-cookie consent plugin.md → content/04-for-plugin-developers/01-tutorial/00-cookie consent plugin.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/01-write the yaml file.md → content/04-for-plugin-developers/01-tutorial/01-write the yaml file.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/02-write the php file.md → content/04-for-plugin-developers/01-tutorial/02-write the php file.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/03-add methods.md → content/04-for-plugin-developers/01-tutorial/03-add methods.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/04-create the structure.md → content/04-for-plugin-developers/01-tutorial/04-create the structure.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/05-add twig template.md → content/04-for-plugin-developers/01-tutorial/05-add twig template.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/06-add default values.md → content/04-for-plugin-developers/01-tutorial/06-add default values.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/07-use variables in twig.md → content/04-for-plugin-developers/01-tutorial/07-use variables in twig.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/08-make variables editable.md → content/04-for-plugin-developers/01-tutorial/08-make variables editable.md


+ 0 - 0
content/04-for plugin developers/01-tutorial/index.md → content/04-for-plugin-developers/01-tutorial/index.md


+ 0 - 0
content/04-for plugin developers/02-documentation/00-file structure.md → content/04-for-plugin-developers/02-documentation/00-file structure.md


+ 0 - 0
content/04-for plugin developers/02-documentation/01-configuration file.md → content/04-for-plugin-developers/02-documentation/01-configuration file.md


+ 0 - 0
content/04-for plugin developers/02-documentation/02-field overview.md → content/04-for-plugin-developers/02-documentation/02-field overview.md


+ 0 - 0
content/04-for plugin developers/02-documentation/03-basic php file.md → content/04-for-plugin-developers/02-documentation/03-basic php file.md


+ 0 - 0
content/04-for plugin developers/02-documentation/04-event overview.md → content/04-for-plugin-developers/02-documentation/04-event overview.md


+ 0 - 0
content/04-for plugin developers/02-documentation/05-method overview.md → content/04-for-plugin-developers/02-documentation/05-method overview.md


+ 0 - 0
content/04-for plugin developers/02-documentation/06-routes.md → content/04-for-plugin-developers/02-documentation/06-routes.md


+ 0 - 0
content/04-for plugin developers/02-documentation/07-middleware.md → content/04-for-plugin-developers/02-documentation/07-middleware.md


+ 0 - 0
content/04-for plugin developers/02-documentation/index.md → content/04-for-plugin-developers/02-documentation/index.md


+ 0 - 0
content/04-for plugin developers/index.md → content/04-for-plugin-developers/index.md


+ 0 - 0
content/05-info/00-release notes.md → content/05-info/00-release-notes.md


+ 0 - 0
content/05-info/01-usage and licence.md → content/05-info/01-usage-and-licence.md


+ 0 - 0
content/05-info/02-Imprint and privacy.md → content/05-info/02-Imprint-and-privacy.md


+ 0 - 0
content/05-info/03-markdown test.md → content/05-info/03-markdown-test.md


+ 203 - 11
system/Controllers/ContentApiController.php

@@ -11,7 +11,7 @@ use Typemill\Extensions\ParsedownExtension;
 class ContentApiController extends ContentController
 {
 	public function publishArticle(Request $request, Response $response, $args)
-	{
+	{		
 		# get params from call 
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri();
@@ -40,7 +40,10 @@ class ContentApiController extends ContentController
 			# update the file
 			$delete = $this->deleteContentFiles(['txt']);
 			
-			# update the structure
+			# update the internal structure
+			$this->setStructure($draft = true, $cache = false);
+			
+			# update the public structure
 			$this->setStructure($draft = false, $cache = false);
 
 			return $response->withJson(['success'], 200);
@@ -52,7 +55,7 @@ class ContentApiController extends ContentController
 	}
 
 	public function unpublishArticle(Request $request, Response $response, $args)
-	{
+	{		
 		# get params from call 
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri();
@@ -99,6 +102,9 @@ class ContentApiController extends ContentController
 		
 		if($delete)
 		{
+			# update the internal structure
+			$this->setStructure($draft = true, $cache = false);
+			
 			# update the live structure
 			$this->setStructure($draft = false, $cache = false);
 			
@@ -116,28 +122,50 @@ class ContentApiController extends ContentController
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri();
 
+		# set url to base path initially
+		$url = $this->uri->getBaseUrl() . '/tm/content';
+		
 		# set structure
 		if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
 
-		# set item 
+		# set item
 		if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
 		
-		# update the file
-		$delete = $this->deleteContentFiles(['md','txt']);
-		
+		if($this->item->elementType == 'file')
+		{
+			$delete = $this->deleteContentFiles(['md','txt']);
+		}
+		elseif($this->item->elementType == 'folder')
+		{
+			$delete = $this->deleteContentFolder();
+		}
+
 		if($delete)
 		{
+			# check if it is a subfile or subfolder and set the redirect-url to the parent item
+			if(count($this->item->keyPathArray) > 1)
+			{
+				# get the parent item
+				$parentItem = Folder::getParentItem($this->structure, $this->item->keyPathArray);
+
+				if($parentItem)
+				{
+					# an active file has been moved to another folder
+					$url .= $parentItem->urlRelWoF;
+				}
+			}
+			
 			# update the live structure
 			$this->setStructure($draft = false, $cache = false);
-			
+				
 			#update the backend structure
 			$this->setStructure($draft = true, $cache = false);
 			
-			return $response->withJson(['success'], 200);
+			return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url), 200);
 		}
 		else
 		{
-			return $response->withJson(['errors' => ['message' => "Could not delete some files. Please check if the files exists and are writable"]], 404);
+			return $response->withJson(array('data' => $this->structure, 'errors' => $this->errors), 404); 
 		}
 	}
 	
@@ -174,6 +202,9 @@ class ContentApiController extends ContentController
 		/* update the file */
 		if($this->write->writeFile($this->settings['contentFolder'], $this->path, $contentJson))
 		{
+			# update the internal structure
+			$this->setStructure($draft = true, $cache = false);
+			
 			return $response->withJson(['success'], 200);
 		}
 		else
@@ -278,10 +309,171 @@ class ContentApiController extends ContentController
 		
 		return $response->withJson(array('data' => $internalStructure, 'errors' => false, 'url' => $url));
 	}
+	
+	public function createArticle(Request $request, Response $response, $args)
+	{
+		# get params from call
+		$this->params 	= $request->getParams();
+		$this->uri 		= $request->getUri();
+		
+		# url is only needed, if an active page is moved
+		$url 			= false;
+		
+		# set structure
+		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
+		
+		# validate input
+		if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 20 chars.', 'url' => $url), 422); }
+		
+		# get the ids (key path) for item, old folder and new folder
+		$folderKeyPath 	= explode('.', $this->params['folder_id']);
+		
+		# get the item from structure
+		$folder			= Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
 
-	public function createBlock(Request $request, Response $response, $args)
+		if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
+		
+		# Rename all files within the folder to make sure, that namings and orders are correct
+		# get the content of the target folder
+		$folderContent	= $folder->folderContent;
+		
+		# create the name for the new item
+		$nameParts = Folder::getStringParts($this->params['item_name']);		
+		$name 		= implode("-", $nameParts);
+		$slug		= $name;
+				
+		# initialize index
+		$index = 0;		
+		
+		# initialise write object
+		$write = new Write();
+
+		# iterate through the whole content of the new folder
+		$writeError = false;
+		
+		foreach($folderContent as $folderItem)
+		{
+			# check, if the same name as new item, then return an error
+			if($folderItem->slug == $slug)
+			{
+				return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
+			}
+			
+			if(!$write->moveElement($folderItem, $folder->path, $index))
+			{
+				$writeError = true;
+			}
+			$index++;
+		}
+
+		if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
+
+		# add prefix number to the name
+		$namePath 	= $index > 9 ? $index . '-' . $name : '0' . $index . '-' . $name;
+		$folderPath	= 'content' . $folder->path;
+		
+		if($this->params['type'] == 'file')
+		{
+			if(!$write->writeFile($folderPath, $namePath . '.txt', ''))
+			{
+				return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
+			}
+		}
+		elseif($this->params['type'] == 'folder')
+		{
+			if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
+			{
+				return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
+			}
+			$write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', '');
+		}
+		
+		# update the structure for editor
+		$this->setStructure($draft = true, $cache = false);
+
+		# get item for url and set it active again
+		if(isset($this->params['url']))
+		{
+			$activeItem = Folder::getItemForUrl($this->structure, $this->params['url']);
+		}
+
+		# activate this if you want to redirect after creating the page...
+		# $url = $this->uri->getBaseUrl() . '/tm/content' . $folder->urlRelWoF . '/' . $name;
+		
+		return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
+	}
+
+	public function createBaseFolder(Request $request, Response $response, $args)
 	{
+		# get params from call
+		$this->params 	= $request->getParams();
+		$this->uri 		= $request->getUri();
 		
+		# url is only needed, if an active page is moved
+		$url 			= false;
+		
+		# set structure
+		if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
+		
+		# validate input
+		#if(!$this->validateBaseFolder()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 20 chars.', 'url' => $url), 422); }
+				
+		# create the name for the new item
+		$nameParts 	= Folder::getStringParts($this->params['item_name']);		
+		$name 		= implode("-", $nameParts);
+		$slug		= $name;
+
+		# initialize index
+		$index = 0;		
+		
+		# initialise write object
+		$write = new Write();
+
+		# iterate through the whole content of the new folder
+		$writeError = false;
+		
+		foreach($this->structure as $folder)
+		{
+			# check, if the same name as new item, then return an error
+			if($folder->slug == $slug)
+			{
+				return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
+			}
+			
+			if(!$write->moveElement($folder, '', $index))
+			{
+				$writeError = true;
+			}
+			$index++;
+		}
+
+		if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
+
+		# add prefix number to the name
+		$namePath 	= $index > 9 ? $index . '-' . $name : '0' . $index . '-' . $name;
+		$folderPath	= 'content';
+		
+		if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
+		{
+			return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
+		}
+		$write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', '');
+		
+		# update the structure for editor
+		$this->setStructure($draft = true, $cache = false);
+
+		# get item for url and set it active again
+		if(isset($this->params['url']))
+		{
+			$activeItem = Folder::getItemForUrl($this->structure, $this->params['url']);
+		}
+
+		return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
+	}
+	
+	
+	public function createBlock(Request $request, Response $response, $args)
+	{
 		/* get params from call */
 		$this->params 	= $request->getParams();
 		$this->uri 		= $request->getUri();

+ 2 - 2
system/Controllers/ContentBackendController.php

@@ -25,10 +25,10 @@ class ContentBackendController extends ContentController
 		$this->params	= isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
 		
 		# set structure
-		if(!$this->setStructure($draft = true)){ return $this->render404($response, array( 'navigation' => true, 'content' => $this->errors )); }
+		if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
 		
 		# set item
-		if(!$this->setItem()){ return $this->render404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
+		if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
 		
 		# get the breadcrumb (here we need it only to mark the actual item active in navigation)
 		$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;

+ 60 - 9
system/Controllers/ContentController.php

@@ -22,7 +22,7 @@ abstract class ContentController
 	protected $uri;
 	
 	# holds the errors to output in frontend 
-	protected $errors;
+	protected $errors = false;
 	
 	# holds a write object to write files 
 	protected $write;
@@ -81,6 +81,11 @@ abstract class ContentController
 		return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
 	}
 	
+	protected function renderIntern404($response, $data = NULL)
+	{
+		return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
+	}	
+		
 	protected function validateEditorInput()
 	{
 		$validate = new Validation();
@@ -106,6 +111,19 @@ abstract class ContentController
 		}
 		return true;
 	}
+
+	protected function validateNaviItem()
+	{
+		$validate = new Validation();
+		$vResult = $validate->navigationItem($this->params);
+		
+		if(is_array($vResult))
+		{
+			$this->errors = ['errors' => $vResult];
+			return false;
+		}
+		return true;
+	}
 	
 	protected function setStructure($draft = false, $cache = true)
 	{
@@ -176,6 +194,7 @@ abstract class ContentController
 			}
 			elseif($item->elementType == 'folder')
 			{
+				$item->pathWithoutItem		= $item->path;
 				$item->path 				= $item->path . DIRECTORY_SEPARATOR . 'index';
 				$item->pathWithoutType		= $item->path;
 			}
@@ -221,24 +240,56 @@ abstract class ContentController
 		}
 	}
 		
-	protected function deleteContentFiles($fileTypes)
+	protected function deleteContentFiles($fileTypes, $folder = false)
 	{
 		$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
-		
+
 		foreach($fileTypes as $fileType)
 		{
-			if(file_exists($basePath . $this->item->pathWithoutType . '.' . $fileType))
+			if(file_exists($basePath . $this->item->pathWithoutType . '.' . $fileType) && !unlink($basePath . $this->item->pathWithoutType . '.' . $fileType) )
 			{
-				unlink($basePath . $this->item->pathWithoutType . '.' . $fileType);
-				
-				# if file could not be deleted
-				# $this->errors = ['errors' => ['message' => 'Could not delete files, please check, if files are writable.']];
-			}			
+				$this->errors = ['message' => 'We could not delete the file, please check, if the file is writable.'];				
+			}
+		}
+		
+		if($this->errors)
+		{
+			return false;
 		}
 		
 		return true;
 	}
 	
+	protected function deleteContentFolder()
+	{
+		$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
+		$path = $basePath . $this->item->pathWithoutItem;
+
+		if(file_exists($path))
+		{
+			$files = array_diff(scandir($path), array('.', '..'));
+			
+			# check if there are folders first, then stop the operation
+			foreach ($files as $file)
+			{
+				if(is_dir(realpath($path) . DIRECTORY_SEPARATOR . $file))
+				{
+					$this->errors = ['message' => 'Please delete the sub-folder first.'];
+				}
+			}
+
+			if(!$this->errors)
+			{
+				foreach ($files as $file)
+				{
+					unlink(realpath($path) . DIRECTORY_SEPARATOR . $file);
+				}
+				return rmdir($path);
+			}
+		}
+		return false;
+	}
+	
 	protected function setContent()
 	{		
 		# if the file exists

+ 37 - 0
system/Models/Validation.php

@@ -54,6 +54,16 @@ class Validation
 			if(!$email){ return false; }
 			return true;
 		}, 'unknown');
+
+		Validator::addRule('noSpecialChars', function($field, $value, array $params, array $fields)
+		{
+			$format = '/[!@#$%^&*()_+=\[\]{};\':"\\|,.<>\/?]/';
+			if ( preg_match($format, $value))
+			{
+				return false;
+			}
+			return true;
+		}, 'contains special characters');
 		
 		Validator::addRule('noHTML', function($field, $value, array $params, array $fields)
 		{
@@ -241,6 +251,33 @@ class Validation
 			return $v->errors();
 		}
 	}
+
+	/**
+	* validation for new navigation items
+	* 
+	* @param array $params with form data.
+	* @return true or $v->errors with array of errors to use in json-response
+	*/
+
+	public function navigationItem(array $params)
+	{
+		$v = new Validator($params);
+						
+		$v->rule('required', ['folder_id', 'item_name', 'type', 'url']);
+		$v->rule('regex', 'folder_id', '/^[0-9.]+$/i');
+		$v->rule('noSpecialChars', 'item_name');
+		$v->rule('lengthBetween', 'item_name', 1, 20);
+		$v->rule('in', 'type', ['file', 'folder']);
+		
+		if($v->validate()) 
+		{
+			return true;
+		} 
+		else
+		{
+			return $v->errors();
+		}
+	}	
 	
 	/**
 	* validation for dynamic fields ( settings for themes and plugins)

+ 5 - 56
system/Models/Write.php

@@ -15,7 +15,7 @@ class Write
 	public function checkPath($folder)
 	{
 		$folderPath = $this->basePath . $folder;
-		
+				
 		if(!is_dir($folderPath))
 		{
 			if(@mkdir($folderPath, 0774, true))
@@ -55,6 +55,7 @@ class Write
 		if($this->checkPath($folder))
 		{
 			$filePath 	= $this->basePath . $folder . DIRECTORY_SEPARATOR . $file;
+			
 			$openFile 	= @fopen($filePath, "w");
 			
 			if(!$openFile)
@@ -79,7 +80,7 @@ class Write
 		}
 		return false;
 	}
-
+	
 	public function moveElement($item, $folderPath, $index)
 	{
 		$filetypes			= array('md', 'txt');
@@ -88,7 +89,7 @@ class Write
 		$newOrder			= ($index < 10) ? '0' . $index : $index;
 
 		# create new path with foldername or filename but without file-type
-		$newPath 			= $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . $item->name;
+		$newPath 			= $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . str_replace(" ", "-", $item->name);
 		
 		if($item->elementType == 'folder')
 		{
@@ -125,58 +126,6 @@ class Write
 			}
 		}
 
-		return $result;
-		
-		/*
-		if($item->elementType == 'folder')
-		{
-			$newName		= $newOrder . '-' . $item->name;
-		}
-		else
-		{
-			$newName		= $newOrder . '-' . $item->name . '.' . $item->fileType;
-		}
-		
-		$oldPath			= $this->basePath . 'content' . $item->path;
-		$newPath 			= $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newName;
-
-		if(@rename($oldPath, $newPath))
-		{
-			$result = true;
-		}
-		
-		foreach($filetypes as $filetype)
-		{
-			#check if file exists
-			if(file_exists($oldPath))
-			{
-
-			}
-		}
-		
-		
-		# if it is a txt file, check, if there is a corresponding .md file and move it
-		if($result && $item->elementType == 'file' && $item->fileType == 'txt')
-		{
-			$result = false;
-			
-			$oldPath		= substr($item->path, 0, strpos($item->path, "."));
-			$oldPath		= $this->basePath . 'content' . $oldPath . '.md';
-
-			if(file_exists($oldPath))
-			{
-				$newName			= $newOrder . '-' . $item->name . '.md';
-				$newPath 			= $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newName;
-				
-				if(@rename($oldPath, $newPath))
-				{
-					$result = true;
-				}
-			}
-		}
-		
-		return $result;
-		*/
-		
+		return $result;		
 	}
 }

+ 5 - 1
system/Routes/Api.php

@@ -6,9 +6,13 @@ use Typemill\Controllers\ContentApiController;
 use Typemill\Middleware\RestrictApiAccess;
 
 $app->get('/api/v1/themes', SettingsController::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router']));
+
 $app->post('/api/v1/article/publish', ContentApiController::class . ':publishArticle')->setName('api.article.publish')->add(new RestrictApiAccess($container['router']));
 $app->delete('/api/v1/article/unpublish', ContentApiController::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new RestrictApiAccess($container['router']));
+$app->post('/api/v1/article', ContentApiController::class . ':createArticle')->setName('api.article.create')->add(new RestrictApiAccess($container['router']));
 $app->put('/api/v1/article', ContentApiController::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
 $app->delete('/api/v1/article', ContentApiController::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
 $app->post('/api/v1/article/sort', ContentApiController::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
-// $app->post('/api/v1/block', ContentBackendController::class . ':createBlock')->setName('api.block.create')->add(new RestrictApiAccess($container['router']));
+$app->post('/api/v1/basefolder', ContentApiController::class . ':createBaseFolder')->setName('api.basefolder.create')->add(new RestrictApiAccess($container['router']));
+
+// $app->post('/api/v1/block', ContentBackendController::class . ':createBlock')->setName('api.block.create')->add(new RestrictApiAccess($container['router']));

+ 24 - 24
system/author/css/fontello/config.json

@@ -6,24 +6,6 @@
   "units_per_em": 1000,
   "ascent": 850,
   "glyphs": [
-    {
-      "uid": "5408be43f7c42bccee419c6be53fdef5",
-      "css": "doc-text",
-      "code": 61686,
-      "src": "fontawesome"
-    },
-    {
-      "uid": "b091a8bd0fdade174951f17d936f51e4",
-      "css": "folder-empty",
-      "code": 61716,
-      "src": "fontawesome"
-    },
-    {
-      "uid": "6533bdc16ab201eb3f3b27ce989cab33",
-      "css": "folder-open-empty",
-      "code": 61717,
-      "src": "fontawesome"
-    },
     {
       "uid": "e99461abfef3923546da8d745372c995",
       "css": "cog",
@@ -36,6 +18,18 @@
       "code": 59393,
       "src": "fontawesome"
     },
+    {
+      "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
+      "css": "off",
+      "code": 59394,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "d7271d490b71df4311e32cdacae8b331",
+      "css": "home",
+      "code": 59395,
+      "src": "fontawesome"
+    },
     {
       "uid": "e15f0d620a7897e2035c18c80142f6d9",
       "css": "link-ext",
@@ -49,15 +43,21 @@
       "src": "fontawesome"
     },
     {
-      "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
-      "css": "off",
-      "code": 59394,
+      "uid": "5408be43f7c42bccee419c6be53fdef5",
+      "css": "doc-text",
+      "code": 61686,
       "src": "fontawesome"
     },
     {
-      "uid": "d7271d490b71df4311e32cdacae8b331",
-      "css": "home",
-      "code": 59395,
+      "uid": "b091a8bd0fdade174951f17d936f51e4",
+      "css": "folder-empty",
+      "code": 61716,
+      "src": "fontawesome"
+    },
+    {
+      "uid": "44e04715aecbca7f266a17d5a7863c68",
+      "css": "plus",
+      "code": 59396,
       "src": "fontawesome"
     }
   ]

+ 2 - 2
system/author/css/fontello/css/fontello-codes.css

@@ -3,8 +3,8 @@
 .icon-picture:before { content: '\e801'; } /* '' */
 .icon-off:before { content: '\e802'; } /* '' */
 .icon-home:before { content: '\e803'; } /* '' */
+.icon-plus:before { content: '\e804'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */
-.icon-folder-empty:before { content: '\f114'; } /* '' */
-.icon-folder-open-empty:before { content: '\f115'; } /* '' */
+.icon-folder-empty:before { content: '\f114'; } /* '' */

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


+ 2 - 2
system/author/css/fontello/css/fontello-ie7-codes.css

@@ -3,8 +3,8 @@
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
 .icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
 .icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&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;'); }
-.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
-.icon-folder-open-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf115;&nbsp;'); }
+.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }

+ 2 - 2
system/author/css/fontello/css/fontello-ie7.css

@@ -14,8 +14,8 @@
 .icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
 .icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
 .icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&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;'); }
-.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
-.icon-folder-open-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf115;&nbsp;'); }
+.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }

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

@@ -1,11 +1,11 @@
 @font-face {
   font-family: 'fontello';
-  src: url('../font/fontello.eot?55717473');
-  src: url('../font/fontello.eot?55717473#iefix') format('embedded-opentype'),
-       url('../font/fontello.woff2?55717473') format('woff2'),
-       url('../font/fontello.woff?55717473') format('woff'),
-       url('../font/fontello.ttf?55717473') format('truetype'),
-       url('../font/fontello.svg?55717473#fontello') format('svg');
+  src: url('../font/fontello.eot?95383779');
+  src: url('../font/fontello.eot?95383779#iefix') format('embedded-opentype'),
+       url('../font/fontello.woff2?95383779') format('woff2'),
+       url('../font/fontello.woff?95383779') format('woff'),
+       url('../font/fontello.ttf?95383779') format('truetype'),
+       url('../font/fontello.svg?95383779#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?55717473#fontello') format('svg');
+    src: url('../font/fontello.svg?95383779#fontello') format('svg');
   }
 }
 */
@@ -59,8 +59,8 @@
 .icon-picture:before { content: '\e801'; } /* '' */
 .icon-off:before { content: '\e802'; } /* '' */
 .icon-home:before { content: '\e803'; } /* '' */
+.icon-plus:before { content: '\e804'; } /* '' */
 .icon-link-ext:before { content: '\f08e'; } /* '' */
 .icon-resize-full-alt:before { content: '\f0b2'; } /* '' */
 .icon-doc-text:before { content: '\f0f6'; } /* '' */
-.icon-folder-empty:before { content: '\f114'; } /* '' */
-.icon-folder-open-empty:before { content: '\f115'; } /* '' */
+.icon-folder-empty:before { content: '\f114'; } /* '' */

+ 7 - 7
system/author/css/fontello/demo.html

@@ -229,11 +229,11 @@ body {
 }
 @font-face {
       font-family: 'fontello';
-      src: url('./font/fontello.eot?16979120');
-      src: url('./font/fontello.eot?16979120#iefix') format('embedded-opentype'),
-           url('./font/fontello.woff?16979120') format('woff'),
-           url('./font/fontello.ttf?16979120') format('truetype'),
-           url('./font/fontello.svg?16979120#fontello') format('svg');
+      src: url('./font/fontello.eot?67933592');
+      src: url('./font/fontello.eot?67933592#iefix') format('embedded-opentype'),
+           url('./font/fontello.woff?67933592') format('woff'),
+           url('./font/fontello.ttf?67933592') format('truetype'),
+           url('./font/fontello.svg?67933592#fontello') format('svg');
       font-weight: normal;
       font-style: normal;
     }
@@ -304,13 +304,13 @@ body {
         <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-home">&#xe803;</i> <span class="i-name">icon-home</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-plus">&#xe804;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe804</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 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 class="row">
-        <div class="the-icons span3" title="Code: 0xf115"><i class="demo-icon icon-folder-open-empty">&#xf115;</i> <span class="i-name">icon-folder-open-empty</span><span class="i-code">0xf115</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>
     <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>

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


+ 2 - 2
system/author/css/fontello/font/fontello.svg

@@ -14,6 +14,8 @@
 
 <glyph glyph-name="home" unicode="&#xe803;" 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="&#xe804;" 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" />
 
 <glyph glyph-name="resize-full-alt" unicode="&#xf0b2;" d="M716 548l-198-198 198-198 80 80q17 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 21 7 38l81 81-198 198-198-198 80-81q17-17 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-14 3-22 9-22 33v250q0 14 11 25t25 11h250q23 0 33-23 9-21-8-38l-80-81 198-198 198 198-81 81q-17 17-7 38 9 23 32 23h250q15 0 26-11t10-25v-250q0-24-22-33-7-3-14-3-14 0-25 11z" horiz-adv-x="857.1" />
@@ -21,8 +23,6 @@
 <glyph glyph-name="doc-text" unicode="&#xf0f6;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
 
 <glyph glyph-name="folder-empty" unicode="&#xf114;" d="M857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" />
-
-<glyph glyph-name="folder-open-empty" unicode="&#xf115;" d="M994 331q0 19-30 19h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q23 0 48 13t40 29l164 203q10 12 10 22z m-637 90h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-90q0-35-25-67l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q52 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-37z" horiz-adv-x="1071.4" />
 </font>
 </defs>
 </svg>

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


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


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


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

@@ -212,7 +212,7 @@ li.menu-item{
 	padding: 0px;
 	position: relative;
 }
-.navi-item i.icon-doc-text, .navi-item i.icon-folder-empty, .navi-item i.icon-home{
+.navi-item i.icon-doc-text, .navi-item i.icon-folder-empty, .navi-item i.icon-home, .navi-item i.icon-plus{
 	display: inline-block;
 	position: absolute;
 	top: 0px;
@@ -220,6 +220,10 @@ li.menu-item{
 	color: #ccc;
 	padding: 7px 2px 7px;
 }
+.navi-item i.txt{
+	background: #e0474c;
+	color: #fff;
+}
 .navi-item i.icon-resize-full-alt{
 	position: absolute;
 	right: 5px;
@@ -231,7 +235,6 @@ li.menu-item{
 	padding: 7px 0;
 	width: 100%;
 	margin-bottom: 2px;
-	box-shadow: 0 0 1px #ddd;
 	text-decoration: none;
 	color: #444;
 	background: #fff;
@@ -261,6 +264,64 @@ span.level-3{ padding-left: 50px; }
 span.level-4{ padding-left: 60px; }
 span.level-5{ padding-left: 70px; }
 
+.addNaviItem{
+	padding: 5px;
+	display: block;
+}
+.addNaviItem .hide{
+	height: 0px;
+	overflow: hidden;
+}
+a.addNaviLink, a.addNaviLink:link, a.addNaviLink:visited{
+	padding: 3px 0;
+	margin: 0;
+	width: auto;
+	background: transparent;
+	color: #e0474c;
+}
+a.addNaviLink:focus, a.addNaviLink:hover, a.addNaviLink:active{
+	text-decoration: underline;
+}
+.addNaviForm input{
+	min-height: 0px;
+	width: 100%;
+	background: #fff;
+	padding: 7px;
+	outline: 1px solid #efefef;
+}
+.addNaviForm button{
+	display: inline-block;
+	box-sizing: border-box;
+	margin: 5px 0 10px;
+	padding: 5px;
+	background: #e0474c;
+	color: #f9f8f6;
+	width: 50%;
+	border: 0px;
+	border-radius: 2px;
+}
+.addNaviForm button.fullWidth{
+	width: 100%;
+}
+.addNaviForm button:hover{
+	background: #cc4146;
+}
+.addNaviForm button.b-left{
+	border-right: 1px solid #f9f8f6;
+}
+.addNaviForm button.b-right{
+	border-left: 1px solid #f9f8f6;
+}
+.fade-enter-active {
+  transition: opacity 0.25s ease-out;
+}
+.fade-leave-active{
+  transition: opacity 0.1s ease-out;	
+}
+.fade-enter, .fade-leave-to {
+  opacity: 0;
+}
+
 /********************
 *   	CONTENT		*
 ********************/
@@ -1393,7 +1454,7 @@ label .help, .label .help{
 	span.level-3{ padding-left: 35px; }
 	span.level-4{ padding-left: 50px; }
 	span.level-5{ padding-left: 65px; }
-	.navi-item i.icon-doc-text, .navi-item i.icon-folder-empty, .navi-item i.icon-home{
+	.navi-item i.icon-doc-text, .navi-item i.icon-folder-empty, .navi-item i.icon-home, .navi-item i.icon-plus{
 		left: -27px;
 	}	
 	fieldset.plugin{

+ 10 - 0
system/author/intern404.twig

@@ -0,0 +1,10 @@
+{% extends 'layouts/layoutBlank.twig' %}
+
+{% block title %}ERROR 404: Page not found{% endblock %}
+
+{% block content %}
+
+	<h1>Not Found</h1>
+	<p>Sorry, but we did not find the page that you are looking for.</p>
+	
+{% endblock %}

+ 2 - 2
system/author/js/vue-editor.js

@@ -168,10 +168,10 @@ let editor = new Vue({
 						self.modalWindow = "modal";
 						if(result.errors.message){ self.errors.message = result.errors.message };
 					}
-					else
+					else if(result.url)
 					{
 						self.modalWindow = "modal";
-						window.location.replace(self.root + '/tm/content');
+						window.location.replace(result.url);
 					}
 				}
 			}, method, url, this.form );

+ 128 - 15
system/author/js/vue-navi.js

@@ -1,6 +1,11 @@
 const navcomponent = Vue.component('navigation', {
 	template: '#navigation-template',
-	props: ['name', 'parent', 'active', 'filetype', 'element', 'folder', 'level', 'url', 'root', 'freeze'],
+	props: ['name', 'newItem', 'parent', 'active', 'filetype', 'elementtype', 'element', 'folder', 'level', 'url', 'root', 'freeze'],
+	data: function () {
+		return {
+			showForm: false
+		}
+	},
 	methods: {
 		checkMove : function(evt)
 		{			
@@ -26,14 +31,14 @@ const navcomponent = Vue.component('navigation', {
 				'item_id': 			evt.item.id,
 				'parent_id_from': 	evt.from.parentNode.id, 
 				'parent_id_to': 	evt.to.parentNode.id, 
-				'index_old': 		evt.oldIndex, 
+				'index_old': 		evt.oldIndex,
 				'index_new': 		evt.newIndex,
 				'active':			evt.item.firstChild.className,
 				'url':				document.getElementById("path").value,
 				'csrf_name': 		document.getElementById("csrf_name").value,
 				'csrf_value':		document.getElementById("csrf_value").value,				
 			};
-			
+						
 			if(locator.parent_id_from == locator.parent_id_to && locator.index_old == locator.index_new)
 			{
 				return
@@ -82,15 +87,15 @@ const navcomponent = Vue.component('navigation', {
 			level = level.split('.').length;
 			return 'level-' + level;
 		},
-		getIcon : function(filetype)
+		getIcon : function(elementtype, filetype)
 		{
-			if(filetype == 'file')
+			if(elementtype == 'file')
 			{
-				return 'icon-doc-text'
+				return 'icon-doc-text ' + filetype
 			}
-			if(filetype == 'folder')
+			if(elementtype == 'folder')
 			{
-				return 'icon-folder-empty'
+				return 'icon-folder-empty ' + filetype
 			}
 		},
 		checkActive : function(active,parent)
@@ -100,7 +105,64 @@ const navcomponent = Vue.component('navigation', {
 				return 'active';
 			}
 			return 'inactive';
-		}
+		},
+		toggleForm : function()
+		{
+			this.showForm = !this.showForm;
+		},
+		addFile : function(type)
+		{
+			editor.errors.message = false;
+
+			if(this.$root.$data.format.test(this.newItem) || !this.newItem || this.newItem.length > 20)
+			{ 
+				editor.errors.message = 'Special Characters are not allowed. Length between 1 and 20.';
+				return;
+			}
+			
+			var newItem = {
+				'folder_id': 		this.$el.id,
+				'item_name': 		this.newItem,
+				'type':				type,
+				'url':				document.getElementById("path").value,
+				'csrf_name': 		document.getElementById("csrf_name").value,
+				'csrf_value':		document.getElementById("csrf_value").value,
+			};
+			
+			// evt.item.classList.add("load");
+			
+			var self = this;
+			
+			self.$root.$data.freeze = true;
+			self.errors = {title: false, content: false, message: false};
+			
+			var url = this.root + '/api/v1/article';
+			var method 	= 'POST';
+
+			sendJson(function(response, httpStatus)
+			{
+				if(response)
+				{
+					self.$root.$data.freeze = false;
+					var result = JSON.parse(response);
+					
+					if(result.errors)
+					{
+						editor.errors.message = result.errors;
+					}
+					if(result.url)
+					{
+						window.location.replace(result.url);
+					}
+					if(result.data)
+					{
+						// evt.item.classList.remove("load");
+						self.$root.$data.items = result.data;						
+						self.showForm = false;
+					}
+				}
+			}, method, url, newItem );
+		},
 	}
 })
 
@@ -109,12 +171,16 @@ let navi = new Vue({
 	components: {
 		'navcomponent': navcomponent,
 	},
-	data: {
-		title: "Navigation",
-		items: JSON.parse(document.getElementById("data-navi").dataset.navi),
-		root: document.getElementById("main").dataset.url,
-		freeze: false,
-		modalWindow: "modal hide",		
+	data: function () {
+		return {
+			title: "Navigation",
+			items: JSON.parse(document.getElementById("data-navi").dataset.navi),
+			root: document.getElementById("main").dataset.url,
+			freeze: false,
+			modalWindow: "modal hide",
+			format: /[!@#$%^&*()_+=\[\]{};':"\\|,.<>\/?]/,
+			folderName: '',
+		}
 	},
 	methods:{
 		onStart: function(evt){
@@ -129,5 +195,52 @@ let navi = new Vue({
 		hideModal: function(e){
 			this.modalWindow = "modal";
 		},
+		addFolder: function()
+		{
+			editor.errors.message = false;
+
+			if(this.format.test(this.folderName) || this.folderName < 1 || this.folderName.length > 20)
+			{ 
+				editor.errors.message = 'Special Characters are not allowed. Length between 1 and 20.';
+				return;
+			}
+			
+			var newFolder = {
+				'item_name': 		this.folderName,
+				'url':				document.getElementById("path").value,
+				'csrf_name': 		document.getElementById("csrf_name").value,
+				'csrf_value':		document.getElementById("csrf_value").value,
+			};
+						
+			var self = this;
+			
+			self.freeze = true;
+			self.errors = {title: false, content: false, message: false};
+			
+			var url = this.root + '/api/v1/basefolder';
+			var method 	= 'POST';
+
+			sendJson(function(response, httpStatus)
+			{
+				if(response)
+				{
+					self.freeze = false;
+					var result = JSON.parse(response);
+					
+					if(result.errors)
+					{
+						editor.errors.message = result.errors;
+					}
+					if(result.url)
+					{
+						window.location.replace(result.url);
+					}
+					if(result.data)
+					{
+						self.items = result.data;						
+					}
+				}
+			}, method, url, newFolder );
+		}
 	}
 })

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

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="UTF-8">
+		<title>{% block title %}{% endblock %}</title>
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+		<meta name="description" content="Configure your TYPEMILL website"/>
+		
+		<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" />
+		<link rel="icon" type="image/png" href="{{ base_url }}/system/author/img/favicon-16x16.png" sizes="16x16" />
+		<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ base_url }}/system/author/img/apple-touch-icon-144x144.png" />
+		<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ base_url }}/system/author/img/apple-touch-icon-152x152.png" />		
+		
+		<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/style.css" />
+	</head>
+	<body>	
+		<header class="main-header">
+			{% include 'partials/navi.twig' %}
+		</header>
+		{% include 'partials/flash.twig' %}
+		<div class="main" id="main" data-url="{{ base_url }}">
+			<aside class="sidebar"></aside>
+			<article>
+				{% block content %}{% endblock %}
+			</article>
+			<footer></footer>
+		</div>
+	</body>
+</html>

+ 13 - 4
system/author/partials/editorNavi.twig

@@ -9,8 +9,17 @@
 			</div>
 		</div>
 		<draggable :element="'ul'" class="navi-list" :list="items" @start="onStart" @end="onEnd" :options="{group:{ name:'folder'}, animation: 150, 'disabled': freeze }">
-			<navigation ref="draggit" v-for="item in items" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :root="root" :url="item.urlRelWoF" v-bind:id="item.keyPath" :key="item.keyPath" :filetype="item.elementType" :folder="item.folderContent"></navigation>
+			<navigation ref="draggit" v-for="item in items" :freeze="freeze" :name="item.name" :active="item.active" :parent="item.activeParent" :level="item.keyPath" :root="root" :url="item.urlRelWoF" v-bind:id="item.keyPath" :key="item.keyPath" :elementtype="item.elementType" :filetype="item.fileType" :folder="item.folderContent"></navigation>
 		</draggable>
+		<ul class="navi-list addBaseFolder">
+			<li class="navi-item file">
+				<i class="icon-plus"></i>
+				<div class="addNaviForm">
+					<input type="text" v-model="folderName" />
+					<button class="fullWidth" @click="addFolder">add folder to base level</button>
+				</div>
+			</li>
+		</ul>
 		
 		<div id="modalWindow" :class="modalWindow">
 			<div class="modalInner wide">
@@ -32,11 +41,11 @@
 
 {% verbatim %}
 	<template id="navigation-template">
-		<li class="navi-item" :class="filetype"><a v-bind:href="getUrl(root, url)" :class="checkActive(active,parent)"><i :class="getIcon(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-resize-full-alt"></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 }">
-				<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.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>
-		<!--	<div v-if="folder">+ add</div> -->
+			<ul v-if="folder" class="navi-list"><li class="navi-item file"><i class="icon-plus"></i><span :class="getLevel(level + '.0')" class="addNaviItem"><a class="addNaviLink" href="#" @click.prevent="toggleForm">add item</a></span><transition name="fade"><div v-if="showForm" class="addNaviForm"><input v-model="newItem"><button class="b-left" @click="addFile('file')">add file</button><button class="b-right" @click="addFile('folder')">add folder</button></div></transition></li></ul>
 		</li>
 	</template>
 {% endverbatim %}

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