commit da1ca33ddfaded9c694ff1c44a696df3f610b8f0
parent 20db055f024bf02a918780cc2a2d1ad8391eedbc
Author: markseu <mark2011@mayberg.se>
Date: Sun, 29 Apr 2018 17:48:46 +0200
Updated editor, simple file upload
Diffstat:
4 files changed, 243 insertions(+), 26 deletions(-)
diff --git a/system/config/config.ini b/system/config/config.ini
@@ -54,6 +54,8 @@ MultiLanguageMode: 0
InstallationMode: 1
StartupUpdate: none
EditLocation: /edit/
+EditUploadNewLocation: /media/@group/@filename
+EditUploadExtensions: .gif, .jpg, .pdf, .png, .svg, .tgz, .zip
EditKeyboardShortcuts: ctrl+b bold, ctrl+i italic, ctrl+e code, ctrl+k link, ctrl+s save, ctrl+shift+p preview
EditToolbarButtons: auto
EditEndOfLine: auto
diff --git a/system/plugins/edit.js b/system/plugins/edit.js
@@ -9,6 +9,8 @@ var yellow =
onClickAction: function(e) { yellow.edit.clickAction(e); },
onClick: function(e) { yellow.edit.click(e); },
onKeydown: function(e) { yellow.edit.keydown(e); },
+ onDrag: function(e) { yellow.edit.drag(e); },
+ onDrop: function(e) { yellow.edit.drop(e); },
onUpdate: function() { yellow.edit.updatePane(yellow.edit.paneId, yellow.edit.paneAction, yellow.edit.paneStatus); },
onResize: function() { yellow.edit.resizePane(yellow.edit.paneId, yellow.edit.paneAction, yellow.edit.paneStatus); }
};
@@ -92,6 +94,23 @@ yellow.edit =
if(this.paneId && e.keyCode==27) this.hidePane(this.paneId);
},
+ // Handle drag
+ drag: function(e)
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ },
+
+ // Handle drop
+ drop: function(e)
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ var elementText = document.getElementById("yellow-pane-edit-text");
+ var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
+ for(var i=0; i<files.length; i++) this.uploadFile(elementText, files[i]);
+ },
+
// Create bar
createBar: function(barId)
{
@@ -136,6 +155,9 @@ yellow.edit =
if(paneId=="yellow-pane-edit")
{
yellow.toolbox.addEvent(elementPane, "input", yellow.onUpdate);
+ yellow.toolbox.addEvent(elementPane, "dragenter", yellow.onDrag);
+ yellow.toolbox.addEvent(elementPane, "dragover", yellow.onDrag);
+ yellow.toolbox.addEvent(elementPane, "drop", yellow.onDrop);
}
if(paneId=="yellow-pane-edit" || paneId=="yellow-pane-user")
{
@@ -568,9 +590,9 @@ yellow.edit =
case "ol": yellow.editor.setMarkdown(elementText, "1. ", "insert-multiline-block", true); break;
case "tl": yellow.editor.setMarkdown(elementText, "- [ ] ", "insert-multiline-block", true); break;
case "link": yellow.editor.setMarkdown(elementText, "[link](url)", "insert", false, yellow.editor.getMarkdownLink); break;
- case "file": yellow.editor.setMarkdown(elementText, "[image picture.jpg]", "insert"); break;
case "text": yellow.editor.setMarkdown(elementText, args, "insert"); break;
case "draft": yellow.editor.setMetaData(elementText, "status", "draft", true); break;
+ case "file": this.showFileDialog(); break;
case "undo": yellow.editor.undo(); break;
case "redo": yellow.editor.redo(); break;
}
@@ -720,23 +742,23 @@ yellow.edit =
formData.append("rawdataendofline", yellow.page.rawDataEndOfLine);
var request = new XMLHttpRequest();
request.open("POST", window.location.pathname, true);
- request.onload = function() { if(this.status==200) thisObject.updatePreview.call(thisObject, elementText, elementPreview, this.responseText); };
+ request.onload = function() { if(this.status==200) thisObject.showPreviewDone.call(thisObject, elementText, elementPreview, this.responseText); };
request.send(formData);
} else {
- this.updatePreview(elementText, elementPreview, "");
+ this.showPreviewDone(elementText, elementPreview, "");
}
},
- // Update preview
- updatePreview: function(elementText, elementPreview, string)
+ // Preview done
+ showPreviewDone: function(elementText, elementPreview, responseText)
{
- var showPreview = string.length!=0;
+ var showPreview = responseText.length!=0;
yellow.toolbox.setVisible(elementText, !showPreview);
yellow.toolbox.setVisible(elementPreview, showPreview);
if(showPreview)
{
this.updateToolbar("preview", "yellow-toolbar-checked");
- elementPreview.innerHTML = string;
+ elementPreview.innerHTML = responseText;
dispatchEvent(new Event("load"));
} else {
this.updateToolbar(0, "yellow-toolbar-checked");
@@ -744,6 +766,68 @@ yellow.edit =
}
},
+ // Show file dialog and trigger upload
+ showFileDialog: function()
+ {
+ var element = document.createElement("input");
+ element.setAttribute("id", "yellow-file-dialog");
+ element.setAttribute("type", "file");
+ element.setAttribute("accept", yellow.config.editUploadExtensions);
+ element.setAttribute("multiple", "multiple");
+ yellow.toolbox.addEvent(element, "change", yellow.onDrop);
+ element.click();
+ },
+
+ // Upload file
+ uploadFile: function(elementText, file)
+ {
+ var extension = (file.name.lastIndexOf(".")!=-1 ? file.name.substring(file.name.lastIndexOf("."), file.name.length) : "").toLowerCase();
+ var extensions = yellow.config.editUploadExtensions.split(/\s*,\s*/);
+ if(file.size<=yellow.config.serverFileSizeMax && extensions.indexOf(extension)!=-1)
+ {
+ var text = this.getText("UploadProgress");
+ yellow.editor.setMarkdown(elementText, text, "insert");
+ var thisObject = this;
+ var formData = new FormData();
+ formData.append("action", "upload");
+ formData.append("file", file);
+ var request = new XMLHttpRequest();
+ request.open("POST", window.location.pathname, true);
+ request.onload = function() { if(this.status==200) { thisObject.uploadFileDone.call(thisObject, elementText, this.responseText); } else { thisObject.uploadFileError.call(thisObject, elementText, this.responseText); } };
+ request.send(formData);
+ }
+ },
+
+ // Upload done
+ uploadFileDone: function(elementText, responseText)
+ {
+ var result = JSON.parse(responseText);
+ if(result)
+ {
+ var textOld = this.getText("UploadProgress");
+ var textNew;
+ if(result.location.substring(0, yellow.config.imageLocation.length)==yellow.config.imageLocation)
+ {
+ textNew = "[image "+result.location.substring(yellow.config.imageLocation.length)+"]";
+ } else {
+ textNew = "[link]("+result.location+")";
+ }
+ yellow.editor.replace(elementText, textOld, textNew);
+ }
+ },
+
+ // Upload error
+ uploadFileError: function(elementText, responseText)
+ {
+ var result = JSON.parse(responseText);
+ if(result)
+ {
+ var textOld = this.getText("UploadProgress");
+ var textNew = "["+result.error+"]";
+ yellow.editor.replace(elementText, textOld, textNew);
+ }
+ },
+
// Bind actions to links
bindActions: function(element)
{
@@ -1046,6 +1130,31 @@ yellow.editor =
return { "text":text, "value":value, "start":start, "end":end, "top":top, "bottom":bottom, "found":found };
},
+ // Replace text
+ replace: function(element, textOld, textNew)
+ {
+ var text = element.value;
+ var selectionStart = element.selectionStart;
+ var selectionEnd = element.selectionEnd;
+ var selectionStartFound = text.indexOf(textOld);
+ var selectionEndFound = selectionStartFound + textOld.length;
+ if(selectionStartFound!=-1)
+ {
+ var selectionStartNew = selectionStart<selectionStartFound ? selectionStart : selectionStart+textNew.length-textOld.length;
+ var selectionEndNew = selectionEnd<selectionEndFound ? selectionEnd : selectionEnd+textNew.length-textOld.length;
+ var textBefore = text.substring(0, selectionStartFound);
+ var textAfter = text.substring(selectionEndFound, text.length);
+ if(textOld!=textNew)
+ {
+ element.focus();
+ element.setSelectionRange(selectionStartFound, selectionEndFound);
+ document.execCommand("insertText", false, textNew);
+ element.value = textBefore + textNew + textAfter;
+ element.setSelectionRange(selectionStartNew, selectionEndNew);
+ }
+ }
+ },
+
// Undo changes
undo: function()
{
diff --git a/system/plugins/edit.php b/system/plugins/edit.php
@@ -5,7 +5,7 @@
class YellowEdit
{
- const VERSION = "0.7.11";
+ const VERSION = "0.7.12";
var $yellow; //access to API
var $response; //web response
var $users; //user accounts
@@ -19,6 +19,8 @@ class YellowEdit
$this->users = new YellowUsers($yellow);
$this->merge = new YellowMerge($yellow);
$this->yellow->config->setDefault("editLocation", "/edit/");
+ $this->yellow->config->setDefault("editUploadNewLocation", "/media/@group/@filename");
+ $this->yellow->config->setDefault("editUploadExtensions", ".gif, .jpg, .pdf, .png, .svg, .tgz, .zip");
$this->yellow->config->setDefault("editKeyboardShortcuts", "ctrl+b bold, ctrl+i italic, ctrl+e code, ctrl+k link, ctrl+s save, ctrl+shift+p preview");
$this->yellow->config->setDefault("editToolbarButtons", "auto");
$this->yellow->config->setDefault("editEndOfLine", "auto");
@@ -208,6 +210,7 @@ class YellowEdit
case "edit": $statusCode = $this->processRequestEdit($scheme, $address, $base, $location, $fileName); break;
case "delete": $statusCode = $this->processRequestDelete($scheme, $address, $base, $location, $fileName); break;
case "preview": $statusCode = $this->processRequestPreview($scheme, $address, $base, $location, $fileName); break;
+ case "upload": $statusCode = $this->processRequestUpload($scheme, $address, $base, $location, $fileName); break;
}
} else {
$this->yellow->lookup->requestHandler = "core";
@@ -713,6 +716,32 @@ class YellowEdit
return $statusCode;
}
+ // Process request to upload file
+ function processRequestUpload($scheme, $address, $base, $location, $fileName)
+ {
+ $data = array();
+ $fileNameTemp = $_FILES["file"]["tmp_name"];
+ $fileNameShort = preg_replace("/[^\pL\d\-\.]/u", "-", basename($_FILES["file"]["name"]));
+ $fileSizeMax = $this->yellow->toolbox->getNumberBytes(ini_get("upload_max_filesize"));
+ $extension = strtoloweru(($pos = strrposu($fileNameShort, '.')) ? substru($fileNameShort, $pos) : "");
+ $extensions = preg_split("/\s*,\s*/", $this->yellow->config->get("editUploadExtensions"));
+ if(!$this->response->isUserRestrictions() && is_uploaded_file($fileNameTemp) &&
+ filesize($fileNameTemp)<=$fileSizeMax && in_array($extension, $extensions))
+ {
+ $file = $this->response->getFileUpload($scheme, $address, $base, $location, $fileNameTemp, $fileNameShort);
+ if(!$file->isError() && $this->yellow->toolbox->renameFile($fileNameTemp, $file->fileName, true))
+ {
+ $data["location"] = $file->getLocation();
+ } else {
+ $data["error"] = "Can't write file '$file->fileName'!";
+ }
+ } else {
+ $data["error"] = "Can't write file '$fileNameShort'!";
+ }
+ $statusCode = $this->yellow->sendData(is_null($data["error"]) ? 200 : 500, json_encode($data), "a.json", false);
+ return $statusCode;
+ }
+
// Check request
function checkRequest($location)
{
@@ -868,13 +897,13 @@ class YellowResponse
$this->editContentFile($page, "create");
if($this->yellow->lookup->isFileLocation($location) || $this->yellow->pages->find($page->location))
{
- $page->location = $this->getLocationNew($page->rawData, $page->location, $page->get("pageNewLocation"));
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"));
$page->fileName = $this->yellow->lookup->findFileNew($page->location, $page->get("published"));
while($this->yellow->pages->find($page->location) || empty($page->fileName))
{
$rawData = $this->yellow->toolbox->setMetaData($page->rawData, "title", $this->getTitleNext($page->rawData));
$page->rawData = $this->normaliseLines($rawData, $endOfLine);
- $page->location = $this->getLocationNew($page->rawData, $page->location, $page->get("pageNewLocation"));
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"));
$page->fileName = $this->yellow->lookup->findFileNew($page->location, $page->get("published"));
if(++$pageCounter>999) break;
}
@@ -912,7 +941,7 @@ class YellowResponse
if(substrb($pageSource->rawData, 0, $pageSource->metaDataOffsetBytes) !=
substrb($page->rawData, 0, $page->metaDataOffsetBytes))
{
- $page->location = $this->getLocationNew($page->rawData, $page->location, $page->get("pageNewLocation"));
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"));
$page->fileName = $this->yellow->lookup->findFileNew($page->location, $page->get("published"));
if($page->location!=$pageSource->location)
{
@@ -965,6 +994,27 @@ class YellowResponse
$page->setOutput($output);
return $page;
}
+
+ // Return uploaded file
+ function getFileUpload($scheme, $address, $base, $pageLocation, $fileNameTemp, $fileNameShort)
+ {
+ $file = new YellowPage($this->yellow);
+ $file->setRequestInformation($scheme, $address, $base, "/".$fileNameTemp, $fileNameTemp);
+ $file->parseData(null, false, 0);
+ $file->set("fileNameShort", $fileNameShort);
+ $this->editMediaFile($file, "upload");
+ $file->location = $this->getFileNewLocation($fileNameShort, $pageLocation, $file->get("fileNewLocation"));
+ $file->fileName = substru($file->location, 1);
+ while(is_file($file->fileName))
+ {
+ $fileNameShort = $this->getFileNext(basename($file->fileName));
+ $file->location = $this->getFileNewLocation($fileNameShort, $pageLocation, $file->get("fileNewLocation"));
+ $file->fileName = substru($file->location, 1);
+ if(++$fileCounter>999) break;
+ }
+ if(is_file($file->fileName)) $file->error(500, "File '".$file->get("fileNameShort")."' is not possible!");
+ return $file;
+ }
// Return page data including login information
function getPageData()
@@ -1007,6 +1057,7 @@ class YellowResponse
$data["serverScheme"] = $this->yellow->config->get("serverScheme");
$data["serverAddress"] = $this->yellow->config->get("serverAddress");
$data["serverBase"] = $this->yellow->config->get("serverBase");
+ $data["serverFileSizeMax"] = $this->yellow->toolbox->getNumberBytes(ini_get("upload_max_filesize"));
$data["serverVersion"] = "Datenstrom Yellow ".YellowCore::VERSION;
$data["serverPlugins"] = array();
foreach($this->yellow->plugins->plugins as $key=>$value)
@@ -1018,6 +1069,7 @@ class YellowResponse
{
$data["serverLanguages"][$language] = $this->yellow->text->getTextHtml("languageDescription", $language);
}
+ $data["editUploadExtensions"] = $this->yellow->config->get("editUploadExtensions");
$data["editKeyboardShortcuts"] = $this->yellow->config->get("editKeyboardShortcuts");
$data["editToolbarButtons"] = $this->getToolbarButtons("edit");
$data["emojiawesomeToolbarButtons"] = $this->getToolbarButtons("emojiawesome");
@@ -1117,17 +1169,17 @@ class YellowResponse
}
// Return location for new/modified page
- function getLocationNew($rawData, $pageLocation, $pageNewLocation)
+ function getPageNewLocation($rawData, $pageLocation, $pageNewLocation)
{
$location = empty($pageNewLocation) ? "@title" : $pageNewLocation;
- $location = preg_replace("/@timestamp/i", $this->getLocationDataNew($rawData, "published", true, "U"), $location);
- $location = preg_replace("/@date/i", $this->getLocationDataNew($rawData, "published", true, "Y-m-d"), $location);
- $location = preg_replace("/@year/i", $this->getLocationDataNew($rawData, "published", true, "Y"), $location);
- $location = preg_replace("/@month/i", $this->getLocationDataNew($rawData, "published", true, "m"), $location);
- $location = preg_replace("/@day/i", $this->getLocationDataNew($rawData, "published", true, "d"), $location);
- $location = preg_replace("/@tag/i", $this->getLocationDataNew($rawData, "tag", true), $location);
- $location = preg_replace("/@author/i", $this->getLocationDataNew($rawData, "author", true), $location);
- $location = preg_replace("/@title/i", $this->getLocationDataNew($rawData, "title"), $location);
+ $location = preg_replace("/@timestamp/i", $this->getPageNewData($rawData, "published", true, "U"), $location);
+ $location = preg_replace("/@date/i", $this->getPageNewData($rawData, "published", true, "Y-m-d"), $location);
+ $location = preg_replace("/@year/i", $this->getPageNewData($rawData, "published", true, "Y"), $location);
+ $location = preg_replace("/@month/i", $this->getPageNewData($rawData, "published", true, "m"), $location);
+ $location = preg_replace("/@day/i", $this->getPageNewData($rawData, "published", true, "d"), $location);
+ $location = preg_replace("/@tag/i", $this->getPageNewData($rawData, "tag", true), $location);
+ $location = preg_replace("/@author/i", $this->getPageNewData($rawData, "author", true), $location);
+ $location = preg_replace("/@title/i", $this->getPageNewData($rawData, "title"), $location);
if(!preg_match("/^\//", $location))
{
$location = $this->yellow->lookup->getDirectoryLocation($pageLocation).$location;
@@ -1135,8 +1187,8 @@ class YellowResponse
return $location;
}
- // Return location data for new/modified page
- function getLocationDataNew($rawData, $key, $filterFirst = false, $dateFormat = "")
+ // Return data for new/modified page
+ function getPageNewData($rawData, $key, $filterFirst = false, $dateFormat = "")
{
$value = $this->yellow->toolbox->getMetaData($rawData, $key);
if($filterFirst && preg_match("/^(.*?)\,(.*)$/", $value, $matches)) $value = $matches[1];
@@ -1145,8 +1197,42 @@ class YellowResponse
$value = $this->yellow->lookup->normaliseName($value, true, false, true);
return trim(preg_replace("/-+/", "-", $value), "-");
}
+
+ // Return location for new file
+ function getFileNewLocation($fileNameShort, $pageLocation, $fileNewLocation)
+ {
+ $location = empty($fileNewLocation) ? $this->yellow->config->get("editUploadNewLocation") : $fileNewLocation;
+ $location = preg_replace("/@timestamp/i", time(), $location);
+ $location = preg_replace("/@type/i", $this->yellow->toolbox->getFileType($fileNameShort), $location);
+ $location = preg_replace("/@group/i", $this->getFileNewGroup($fileNameShort), $location);
+ $location = preg_replace("/@folder/i", $this->getFileNewFolder($pageLocation), $location);
+ $location = preg_replace("/@filename/i", strtoloweru($fileNameShort), $location);
+ if(!preg_match("/^\//", $location))
+ {
+ $location = $this->yellow->config->get("mediaLocation").$location;
+ }
+ return $location;
+ }
+
+ // Return group for new file
+ function getFileNewGroup($fileNameShort)
+ {
+ $path = $this->yellow->config->get("mediaDir");
+ $fileType = $this->yellow->toolbox->getFileType($fileNameShort);
+ $fileName = $this->yellow->config->get(preg_match("/(gif|jpg|png|svg)$/", $fileType) ? "imageDir" : "downloadDir").$fileNameShort;
+ preg_match("#^$path(.+?)\/#", $fileName, $matches);
+ return strtoloweru($matches[1]);
+ }
+
+ // Return folder for new file
+ function getFileNewFolder($pageLocation)
+ {
+ $parentTopLocation = $this->yellow->pages->getParentTopLocation($pageLocation);
+ if($parentTopLocation==$this->yellow->pages->getHomeLocation($pageLocation)) $parentTopLocation .= "home";
+ return strtoloweru(trim($parentTopLocation, '/'));
+ }
- // Return title for next page
+ // Return next title
function getTitleNext($rawData)
{
preg_match("/^(.*?)(\d*)$/", $this->yellow->toolbox->getMetaData($rawData, "title"), $matches);
@@ -1154,6 +1240,16 @@ class YellowResponse
$titleNumber = strempty($matches[2]) ? " 2" : $matches[2]+1;
return $titleText.$titleNumber;
}
+
+ // Return next file name
+ function getFileNext($fileNameShort)
+ {
+ preg_match("/^(.*?)(\d*)(\..*?)?$/", $fileNameShort, $matches);
+ $fileText = $matches[1];
+ $fileNumber = strempty($matches[2]) ? "-2" : $matches[2]+1;
+ $fileExtension = $matches[3];
+ return $fileText.$fileNumber.$fileExtension;
+ }
// Normalise text lines, convert line endings
function normaliseLines($text, $endOfLine = "lf")
@@ -1222,7 +1318,7 @@ class YellowResponse
return mail($mailTo, $mailSubject, $mailMessage, $mailHeaders);
}
- // Edit content file
+ // Change content file
function editContentFile($page, $action)
{
if(!$page->isError())
@@ -1233,6 +1329,18 @@ class YellowResponse
}
}
}
+
+ // Change media file
+ function editMediaFile($file, $action)
+ {
+ if(!$file->isError())
+ {
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onEditMediaFile")) $value["obj"]->onEditMediaFile($file, $action);
+ }
+ }
+ }
// Check if active
function isActive()
@@ -1288,7 +1396,6 @@ class YellowUsers
if(!empty($matches[1]) && !empty($matches[2]))
{
list($hash, $name, $language, $status, $modified, $errors, $pending, $home) = explode(',', $matches[2]);
- if($errors=="none") { $home=$pending; $pending=$errors; $errors="0"; } //TODO: remove later, converts old file format
$this->set($matches[1], $hash, $name, $language, $status, $modified, $errors, $pending, $home);
if(defined("DEBUG") && DEBUG>=3) echo "YellowUsers::load email:$matches[1]<br/>\n";
}
@@ -1305,7 +1412,6 @@ class YellowUsers
if(!empty($matches[1]) && !empty($matches[2]))
{
list($hash, $name, $language, $status, $modified, $errors, $pending, $home) = explode(',', $matches[2]);
- if($errors=="none") { $home=$pending; $pending=$errors; $errors="0"; } //TODO: remove later, converts old file format
if($status=="active" || $status=="inactive")
{
$pending = "none";
diff --git a/system/plugins/edit.woff b/system/plugins/edit.woff
Binary files differ.