mikuli.cz

:)
git clone https://git.sr.ht/~ashymad/mikuli.cz
Log | Files | Refs

commit da1ca33ddfaded9c694ff1c44a696df3f610b8f0
parent 20db055f024bf02a918780cc2a2d1ad8391eedbc
Author: markseu <mark2011@mayberg.se>
Date:   Sun, 29 Apr 2018 17:48:46 +0200

Updated editor, simple file upload

Diffstat:
Msystem/config/config.ini | 2++
Msystem/plugins/edit.js | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msystem/plugins/edit.php | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msystem/plugins/edit.woff | 0
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.