mikuli.cz

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

commit ef97347fe2241465ddad2fabbd8e0cbd72da4cce
parent 8c5d7e58d9d58703ac254df90a8b35fe6ec44061
Author: markseu <mark2011@mayberg.se>
Date:   Sun, 15 Apr 2018 14:45:50 +0200

Updated system, spring remix with a better editor

Diffstat:
Msystem/config/config.ini | 1+
Msystem/plugins/core.php | 2+-
Msystem/plugins/edit.css | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msystem/plugins/edit.js | 686++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msystem/plugins/edit.php | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Asystem/plugins/edit.woff | 0
Msystem/plugins/language-de.txt | 35+++++++++++++++++++++++++++++------
Msystem/plugins/language-en.txt | 35+++++++++++++++++++++++++++++------
Msystem/plugins/language-fr.txt | 35+++++++++++++++++++++++++++++------
Msystem/plugins/language.php | 2+-
Msystem/themes/assets/flatsite.css | 7++++---
Msystem/themes/assets/flatsite.php | 4++--
12 files changed, 1006 insertions(+), 126 deletions(-)

diff --git a/system/config/config.ini b/system/config/config.ini @@ -55,6 +55,7 @@ MultiLanguageMode: 0 InstallationMode: 1 StartupUpdate: none EditLocation: /edit/ +EditToolbarButtons: auto EditEndOfLine: auto EditUserFile: user.ini EditUserPasswordMinLength: 4 diff --git a/system/plugins/core.php b/system/plugins/core.php @@ -5,7 +5,7 @@ class YellowCore { - const VERSION = "0.7.2"; + const VERSION = "0.7.3"; var $page; //current page var $pages; //pages from file system var $files; //files from file system diff --git a/system/plugins/edit.css b/system/plugins/edit.css @@ -2,7 +2,7 @@ /* Copyright (c) 2013-2018 Datenstrom, https://datenstrom.se */ /* This file may be used and distributed under the terms of the public license. */ -.yellow-bar { position:relative; overflow:hidden; line-height:2em; margin-bottom:10px; } +.yellow-bar { position:relative; line-height:2em; margin-bottom:10px; } .yellow-bar-left { display:block; float:left; } .yellow-bar-right { display:block; float:right; } .yellow-bar-right a { margin-left:1em; } @@ -18,19 +18,20 @@ } .yellow-pane h1 { color:#000; font-size:2em; margin:0 1em; } .yellow-pane p { margin:0.5em; } -.yellow-pane div { overflow:hidden; } .yellow-close { position:absolute; - top:0.4em; right:0.8em; cursor:pointer; - font-size:1.2em; color:#bbb; text-decoration:none; } + top:0.8em; right:1em; cursor:pointer; + font-size:0.9em; color:#bbb; text-decoration:none; +} .yellow-close:hover { color:#000; text-decoration:none; } .yellow-arrow { position:absolute; top:0; left:0; } .yellow-arrow:after, .yellow-arrow:before { position:absolute; + pointer-events:none; bottom:100%; height:0; width:0; border:solid transparent; - content:" "; + content:""; } .yellow-arrow:after { border-color:rgba(255, 255, 255, 0); @@ -44,10 +45,88 @@ border-width:11px; margin-left:-11px; } +.yellow-popup { + position:absolute; display:none; z-index:200; padding:10px 0; + background-color:#fff; color:#000; + border:1px solid #bbb; + border-radius:4px; box-shadow:2px 4px 10px rgba(0, 0, 0, 0.2); +} .yellow-dropdown { list-style:none; margin:0; padding:0; } .yellow-dropdown span { display:block; margin:0; padding:0.25em 1em; } .yellow-dropdown a { display:block; padding:0.2em 1em; text-decoration:none; } .yellow-dropdown a:hover { color:#fff; background-color:#18e; text-decoration:none; } +.yellow-dropdown-menu a { color:#000; } +.yellow-toolbar { list-style:none; margin:0; padding:0; } +.yellow-toolbar-left { display:inline-block; float:left; } +.yellow-toolbar-right { display:inline-block; float:right; } +.yellow-toolbar-banner { clear:both; } +.yellow-toolbar li { display:inline-block; vertical-align:top; } +.yellow-toolbar a { + display:inline-block; padding:6px 16px; text-decoration:none; + background-color:#fff; color:#000; + font-size:0.9em; font-weight:normal; + border:1px solid #bbb; + border-radius:4px; +} +.yellow-toolbar a:hover { + background-color:#18e; background-image:none; border-color:#18e; color:#fff; + text-decoration:none; +} +.yellow-toolbar-left a { margin-right:4px; margin-bottom:10px; } +.yellow-toolbar-right a { margin-left:4px; margin-bottom:10px; } +.yellow-toolbar .yellow-icon +{ + font-size:0.9em; min-width:1em; text-align:center; +} +.yellow-toolbar .yellow-toolbar-btn { + padding:6px 10px; min-width:4em; text-align:center; +} +.yellow-toolbar .yellow-toolbar-btn-edit { + background-color:#29f; border-color:#29f; color:#fff; +} +.yellow-toolbar .yellow-toolbar-btn-create { + background-color:#29f; border-color:#29f; color:#fff +} +.yellow-toolbar .yellow-toolbar-btn-delete { + background-color:#e55; border-color:#e55; color:#fff +} +.yellow-toolbar .yellow-toolbar-btn-delete:hover { background-color:#d44; border-color:#d44; } +.yellow-toolbar .yellow-toolbar-btn-separator { visibility:hidden; padding:6px; } +.yellow-toolbar-tooltip { position:relative; } +.yellow-toolbar-tooltip::after, .yellow-toolbar-tooltip::before { + position:absolute; z-index:300; display:none; + pointer-events:none; +} +.yellow-toolbar-tooltip::after { + padding:2px 9px; + font-weight:normal; + font-size:0.9em; + text-align:center; + white-space:nowrap; + content:attr(aria-label); + background-color:#111; color:#ddd; + border-radius:3px; + top:100%; + right:50%; + margin-top:6px; + transform:translateX(50%); +} +.yellow-toolbar-tooltip::before { + width:0; height:0; + content:""; + border:4px solid transparent; + top:auto; + right:50%; + bottom:-6px; + margin-right:-4px; + border-bottom-color:#111; +} +.yellow-toolbar-tooltip:hover::before, .yellow-toolbar-tooltip:hover::after { + display:inline-block; +} +.yellow-toolbar-selected.yellow-toolbar-tooltip::before, .yellow-toolbar-selected.yellow-toolbar-tooltip::after { + display:none; +} .yellow-form-control { margin:0; padding:2px 4px; display:inline-block; @@ -76,12 +155,6 @@ text-decoration:none; } .yellow-btn:active { box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.1); } -.yellow-btn-delete { - background-color:#c33c35; color:#ffffff; - background-image:linear-gradient(to bottom, #ee5f5b, #bd362f); - border-color:#b13121 #b13121 #802020; -} -.yellow-btn-delete:hover, .yellow-btn-delete:focus, .yellow-btn-delete:active { color:#ffffff; } #yellow-pane-login { text-align:center; white-space:nowrap; } #yellow-pane-login .yellow-form-control { width:15em; box-sizing:border-box; } @@ -117,10 +190,150 @@ #yellow-pane-version-fields { text-align:center; margin:0.5em 0; } #yellow-pane-version-buttons { margin-top:-0.5em; } -#yellow-pane-edit h1 { margin:0 0 10px 0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } -#yellow-pane-edit-page { padding:5px; outline:none; resize:none; } -#yellow-pane-edit-buttons { margin-top:5px; } -#yellow-pane-edit-buttons input { margin-right:10px; } -#yellow-pane-edit-help { float:right; } +#yellow-pane-edit-toolbar-title { margin:-5px 0 0 0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +#yellow-pane-edit-text { padding:0 2px; outline:none; resize:none; border:none; } +#yellow-pane-edit-preview { padding:0; overflow:auto; } +#yellow-pane-edit-preview h1 { margin:0.67em 0; } +#yellow-pane-edit-preview p { margin:1em 0; } +#yellow-pane-edit-preview .content { margin:0; padding:0; } #yellow-pane-user { padding:10px 0; } + +#yellow-popup-format, #yellow-popup-heading, #yellow-popup-list { width:16em; } +#yellow-popup-format a, #yellow-popup-heading a { padding:0.25em 16px; } +#yellow-popup-format #yellow-popup-format-h1, #yellow-popup-heading #yellow-popup-heading-h1 { font-size:2em; font-weight:bold; } +#yellow-popup-format #yellow-popup-format-h2, #yellow-popup-heading #yellow-popup-heading-h2 { font-size:1.6em; font-weight:bold; } +#yellow-popup-format #yellow-popup-format-h3, #yellow-popup-heading #yellow-popup-heading-h3 { font-size:1.3em; font-weight:bold; } +#yellow-popup-format #yellow-popup-format-quote { font-style:italic; } +#yellow-popup-format #yellow-popup-format-pre { font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace; font-size:0.9em; line-height:1.8; } +#yellow-popup-emojiawesome { padding:10px; width:14em; } +#yellow-popup-emojiawesome a { padding:0.2em; } +#yellow-popup-emojiawesome .yellow-dropdown li { display:inline-block; } + +#yellow-popup-fontawesome { padding:10px; width:13em; } +#yellow-popup-fontawesome a { padding:0.18em 0.3em; min-width:1em; text-align:center; } +#yellow-popup-fontawesome .yellow-dropdown li { display:inline-block; } + +@font-face { + font-family:'Edit'; + font-weight:normal; + font-style:normal; + src:url('edit.woff') format('woff'); +} +.yellow-icon { + display:inline-block; + font-family:Edit; + font-style:normal; + font-weight:normal; + -webkit-font-smoothing:antialiased; + -moz-osx-font-smoothing:grayscale; +} +.yellow-spin { + -webkit-animation:yellow-spin 1s infinite steps(16); + animation:yellow-spin 1s infinite steps(16); +} +@-webkit-keyframes yellow-spin { + 0% { -webkit-transform:rotate(0deg); transform:rotate(0deg); } + 100% { -webkit-transform:rotate(359deg); transform:rotate(359deg); } +} +@keyframes yellow-spin { + 0% { -webkit-transform:rotate(0deg); transform:rotate(0deg); } + 100% { -webkit-transform:rotate(359deg); transform:rotate(359deg); } +} + +.yellow-icon-preview:before { + content: "\f100"; +} +.yellow-icon-format:before { + content: "\f101"; +} +.yellow-icon-paragraph:before { + content: "\f101"; +} +.yellow-icon-heading:before { + content: "\f102"; +} +.yellow-icon-h1:before { + content: "\f103"; +} +.yellow-icon-h2:before { + content: "\f104"; +} +.yellow-icon-h3:before { + content: "\f105"; +} +.yellow-icon-bold:before { + content: "\f106"; +} +.yellow-icon-italic:before { + content: "\f0f7"; +} +.yellow-icon-strikethrough:before { + content: "\f108"; +} +.yellow-icon-quote:before { + content: "\f109"; +} +.yellow-icon-code:before { + content: "\f10a"; +} +.yellow-icon-pre:before { + content: "\f10a"; +} +.yellow-icon-link:before { + content: "\f10b"; +} +.yellow-icon-file:before { + content: "\f10c"; +} +.yellow-icon-list:before { + content: "\f10d"; +} +.yellow-icon-ul:before { + content: "\f10d"; +} +.yellow-icon-ol:before { + content: "\f10e"; +} +.yellow-icon-tl:before { + content: "\f10f"; +} +.yellow-icon-hr:before { + content: "\f110"; +} +.yellow-icon-table:before { + content: "\f111"; +} +.yellow-icon-emojiawesome:before { + content: "\f112"; +} +.yellow-icon-fontawesome:before { + content: "\f113"; +} +.yellow-icon-draft:before { + content: "\f114"; +} +.yellow-icon-undo:before { + content: "\f115"; +} +.yellow-icon-redo:before { + content: "\f116"; +} +.yellow-icon-spinner:before { + content: "\f200"; +} +.yellow-icon-search:before { + content: "\f201"; +} +.yellow-icon-close:before { + content: "\f202"; +} +.yellow-icon-help:before { + content: "\f203"; +} +.yellow-icon-markdown:before { + content: "\f203"; +} +.yellow-icon-logo:before { + content: "\f8ff"; +} diff --git a/system/plugins/edit.js b/system/plugins/edit.js @@ -19,6 +19,7 @@ yellow.edit = paneActionOld: 0, //previous pane action paneAction: 0, //current pane action paneStatus: 0, //current pane status + popupId: 0, //visible popup ID intervalId: 0, //timer interval ID // Handle initialisation @@ -57,9 +58,10 @@ yellow.edit = case "edit": this.showPane("yellow-pane-edit", action, status, true); break; case "delete": this.showPane("yellow-pane-edit", action, status, true); break; case "user": this.showPane("yellow-pane-user", action, status); break; - case "help": this.hidePane(this.paneId); location.href = this.getText("UserHelpUrl", "yellow"); break; case "send": this.sendPane(this.paneId, this.paneAction); break; case "close": this.hidePane(this.paneId); break; + case "toolbar": this.processToolbar(status, args); break; + case "help": this.processHelp(); break; } }, @@ -79,7 +81,8 @@ yellow.edit = // Handle mouse clicked click: function(e) { - if(this.paneId && !document.getElementById(this.paneId).contains(e.target)) this.hidePane(this.paneId); + if(this.popupId && !document.getElementById(this.popupId).contains(e.target)) this.hidePopup(this.popupId, true); + if(this.paneId && !document.getElementById(this.paneId).contains(e.target)) this.hidePane(this.paneId, true); }, // Handle keyboard @@ -147,7 +150,7 @@ yellow.edit = case "yellow-pane-login": elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1>"+this.getText("LoginTitle")+"</h1>"+ "<div id=\"yellow-pane-login-fields\">"+ "<input type=\"hidden\" name=\"action\" value=\"login\" />"+ @@ -164,7 +167,7 @@ yellow.edit = case "yellow-pane-signup": elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1>"+this.getText("SignupTitle")+"</h1>"+ "<div id=\"yellow-pane-signup-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</div>"+ "<div id=\"yellow-pane-signup-fields\">"+ @@ -182,7 +185,7 @@ yellow.edit = case "yellow-pane-recover": elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1>"+this.getText("RecoverTitle")+"</h1>"+ "<div id=\"yellow-pane-recover-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</div>"+ "<div id=\"yellow-pane-recover-fields-first\">"+ @@ -213,7 +216,7 @@ yellow.edit = } elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1 id=\"yellow-pane-settings-title\">"+this.getText("SettingsTitle")+"</h1>"+ "<div id=\"yellow-pane-settings-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</div>"+ "<div id=\"yellow-pane-settings-fields\">"+ @@ -231,7 +234,7 @@ yellow.edit = case "yellow-pane-version": elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1 id=\"yellow-pane-version-title\">"+yellow.toolbox.encodeHtml(yellow.config.serverVersion)+"</h1>"+ "<div id=\"yellow-pane-version-status\" class=\""+paneStatus+"\">"+this.getText("VersionStatus", "", paneStatus)+"</div>"+ "<div id=\"yellow-pane-version-fields\">"+yellow.page.rawDataOutput+"</div>"+ @@ -241,15 +244,35 @@ yellow.edit = "</form>"; break; case "yellow-pane-edit": + var rawDataButtons = ""; + if(yellow.config.editToolbarButtons && yellow.config.editToolbarButtons!="none") + { + var tokens = yellow.config.editToolbarButtons.split(","); + for(var i=0; i<tokens.length; i++) + { + var token = tokens[i].trim(); + if(token!="separator") + { + rawDataButtons += "<li><a href=\"#\" id=\"yellow-toolbar-"+yellow.toolbox.encodeHtml(token)+"\" class=\"yellow-toolbar-btn-icon yellow-toolbar-tooltip\" data-action=\"toolbar\" data-status=\""+yellow.toolbox.encodeHtml(token)+"\" aria-label=\""+this.getText("Toolbar", "", token)+"\"><i class=\"yellow-icon yellow-icon-"+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>"; + } else { + rawDataButtons += "<li><a href=\"#\" class=\"yellow-toolbar-btn-separator\"></a></li>"; + } + } + if(yellow.config.debug) console.log("yellow.edit.createPane buttons:"+yellow.config.editToolbarButtons); + } elementDiv.innerHTML = "<form method=\"post\">"+ - "<a href=\"#\" class=\"yellow-close\" data-action=\"close\">x</a>"+ - "<h1 id=\"yellow-pane-edit-title\">"+this.getText("Edit")+"</h1>"+ - "<textarea id=\"yellow-pane-edit-page\" class=\"yellow-form-control\" name=\"rawdataedit\"></textarea>"+ - "<div id=\"yellow-pane-edit-buttons\">"+ - "<a href=\"#\" id=\"yellow-pane-edit-send\" class=\"yellow-btn\" data-action=\"send\">"+this.getText("EditButton")+"</a>"+ - "<a href=\""+this.getText("MarkdownHelpUrl", "yellow")+"\" target=\"_blank\" id=\"yellow-pane-edit-help\">"+this.getText("MarkdownHelp")+"</a>" + + "<div id=\"yellow-pane-edit-toolbar\">"+ + "<h1 id=\"yellow-pane-edit-toolbar-title\" class=\"yellow-toolbar yellow-toolbar-left\">"+this.getText("Edit")+"</h1>"+ + "<ul id=\"yellow-pane-edit-toolbar-buttons\" class=\"yellow-toolbar yellow-toolbar-left\">"+rawDataButtons+"</ul>"+ + "<ul id=\"yellow-pane-edit-toolbar-main\" class=\"yellow-toolbar yellow-toolbar-right\">"+ + "<li><a href=\"#\" id=\"yellow-pane-edit-cancel\" class=\"yellow-toolbar-btn\" data-action=\"close\">"+this.getText("CancelButton")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-pane-edit-send\" class=\"yellow-toolbar-btn\" data-action=\"send\">"+this.getText("EditButton")+"</a></li>"+ + "</ul>"+ + "<ul class=\"yellow-toolbar yellow-toolbar-banner\"></ul>"+ "</div>"+ + "<textarea id=\"yellow-pane-edit-text\" class=\"yellow-form-control\"></textarea>"+ + "<div id=\"yellow-pane-edit-preview\"></div>"+ "</form>"; break; case "yellow-pane-user": @@ -310,7 +333,7 @@ yellow.edit = } break; case "yellow-pane-version": - if(paneStatus=="none" && yellow.config.userUpdate) + if(paneStatus=="none" && this.isPlugin("update")) { document.getElementById("yellow-pane-version-status").innerHTML = this.getText("VersionStatusCheck"); document.getElementById("yellow-pane-version-fields").innerHTML = ""; @@ -322,31 +345,42 @@ yellow.edit = } break; case "yellow-pane-edit": + document.getElementById("yellow-pane-edit-text").focus(); if(init) { - var title; - var string = yellow.page.rawDataEdit; - switch(paneAction) + yellow.toolbox.setVisible(document.getElementById("yellow-pane-edit-text"), true); + yellow.toolbox.setVisible(document.getElementById("yellow-pane-edit-preview"), false); + document.getElementById("yellow-pane-edit-toolbar-title").innerHTML = yellow.toolbox.encodeHtml(yellow.page.title); + document.getElementById("yellow-pane-edit-text").value = paneAction=="create" ? yellow.page.rawDataNew : yellow.page.rawDataEdit; + var matches = document.getElementById("yellow-pane-edit-text").value.match(/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+/); + var position = document.getElementById("yellow-pane-edit-text").value.indexOf("\n", matches ? matches[0].length : 0); + document.getElementById("yellow-pane-edit-text").setSelectionRange(position, position); + if(yellow.config.editToolbarButtons!="none") + { + yellow.toolbox.setVisible(document.getElementById("yellow-pane-edit-toolbar-title"), false); + } + if(yellow.config.userRestrictions) { - case "create": title = this.getText("CreateTitle"); string = yellow.page.rawDataNew; break; - case "edit": title = yellow.page.title ? yellow.page.title : this.getText("Edit"); break; - case "delete": title = this.getText("DeleteTitle"); break; + yellow.toolbox.setVisible(document.getElementById("yellow-pane-edit-send"), false); + document.getElementById("yellow-pane-edit-text").readOnly = true; } - document.getElementById("yellow-pane-edit-title").innerHTML = yellow.toolbox.encodeHtml(title); - document.getElementById("yellow-pane-edit-page").value = string; - yellow.toolbox.setCursorPosition(document.getElementById("yellow-pane-edit-page"), 0); } - var key, className, readOnly; - switch(this.getAction(paneId, paneAction)) + if(!yellow.config.userRestrictions) { - case "create": key = "CreateButton"; className = "yellow-btn yellow-btn-create"; readOnly = false; break; - case "edit": key = "EditButton"; className = "yellow-btn yellow-btn-edit"; readOnly = false; break; - case "delete": key = "DeleteButton"; className = "yellow-btn yellow-btn-delete"; readOnly = false; break; - case "": key = "CancelButton"; className = "yellow-btn yellow-btn-cancel"; readOnly = true; break; + var key, className; + switch(this.getAction(paneId, paneAction)) + { + case "create": key = "CreateButton"; className = "yellow-toolbar-btn yellow-toolbar-btn-create"; break; + case "edit": key = "EditButton"; className = "yellow-toolbar-btn yellow-toolbar-btn-edit"; break; + case "delete": key = "DeleteButton"; className = "yellow-toolbar-btn yellow-toolbar-btn-delete"; break; + } + if(document.getElementById("yellow-pane-edit-send").className != className) + { + document.getElementById("yellow-pane-edit-send").innerHTML = this.getText(key); + document.getElementById("yellow-pane-edit-send").className = className; + this.resizePane(paneId, paneAction, paneStatus); + } } - document.getElementById("yellow-pane-edit-send").innerHTML = this.getText(key); - document.getElementById("yellow-pane-edit-send").className = className; - document.getElementById("yellow-pane-edit-page").readOnly = readOnly; break; } this.bindActions(document.getElementById(paneId)); @@ -359,7 +393,7 @@ yellow.edit = var paneLeft = yellow.toolbox.getOuterLeft(elementBar); var paneTop = yellow.toolbox.getOuterTop(elementBar) + yellow.toolbox.getOuterHeight(elementBar) + 10; var paneWidth = yellow.toolbox.getOuterWidth(elementBar); - var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - yellow.toolbox.getOuterHeight(elementBar); + var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - Math.min(yellow.toolbox.getOuterHeight(elementBar) + 10, (yellow.toolbox.getWindowWidth()-yellow.toolbox.getOuterWidth(elementBar))/2); switch(paneId) { case "yellow-pane-login": @@ -376,11 +410,24 @@ yellow.edit = yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-edit"), paneTop); yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit"), paneHeight); yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit"), paneWidth); - yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-page"), yellow.toolbox.getWidth(document.getElementById("yellow-pane-edit"))); + var elementWidth = yellow.toolbox.getWidth(document.getElementById("yellow-pane-edit")); + yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-text"), elementWidth); + yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-preview"), elementWidth); + var buttonsWidth = 0; + var buttonsWidthMax = yellow.toolbox.getOuterWidth(document.getElementById("yellow-pane-edit-toolbar")) - + yellow.toolbox.getOuterWidth(document.getElementById("yellow-pane-edit-toolbar-main")) - 1; + var element = document.getElementById("yellow-pane-edit-toolbar-buttons").firstChild; + for(; element; element=element.nextSibling) + { + element.removeAttribute("style"); + buttonsWidth += yellow.toolbox.getOuterWidth(element); + if(buttonsWidth>buttonsWidthMax) yellow.toolbox.setVisible(element, false); + } + yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-toolbar-title"), buttonsWidthMax); var height1 = yellow.toolbox.getHeight(document.getElementById("yellow-pane-edit")); - var height2 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-content")); - var height3 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-page")); - yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit-page"), height1 - height2 + height3); + var height2 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-toolbar")); + yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit-text"), height1 - height2); + yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit-preview"), height1 - height2); var elementLink = document.getElementById("yellow-pane-"+paneAction+"-link"); var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2; position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-edit")) + 1; @@ -417,61 +464,223 @@ yellow.edit = this.paneId = paneId; this.paneAction = paneAction; this.paneStatus = paneStatus; - this.resizePane(paneId, paneAction, paneStatus); this.updatePane(paneId, paneAction, paneStatus, this.paneActionOld!=this.paneAction); + this.resizePane(paneId, paneAction, paneStatus); } } else { - this.hidePane(this.paneId); + this.hidePane(this.paneId, true); } }, // Hide pane - hidePane: function(paneId) + hidePane: function(paneId, fadeout) { var element = document.getElementById(paneId); if(yellow.toolbox.isVisible(element)) { yellow.toolbox.removeClass(document.body, "yellow-body-modal-open"); yellow.toolbox.removeValue("meta[name=viewport]", "content", ", maximum-scale=1, user-scalable=0"); - yellow.toolbox.setVisible(element, false); + yellow.toolbox.setVisible(element, false, fadeout); this.paneId = 0; this.paneActionOld = this.paneAction; this.paneAction = 0; this.paneStatus = 0; } + this.hidePopup(this.popupId); }, // Send pane sendPane: function(paneId, paneAction, paneStatus, paneArgs) { if(yellow.config.debug) console.log("yellow.edit.sendPane id:"+paneId); + var args = { "action":paneAction }; if(paneId=="yellow-pane-edit") { - paneAction = this.getAction(paneId, paneAction); - if(paneAction) + args.action = this.getAction(paneId, paneAction); + args.rawdatasource = yellow.page.rawDataSource; + args.rawdataedit = document.getElementById("yellow-pane-edit-text").value; + args.rawdataendofline = yellow.page.rawDataEndOfLine; + } + if(paneArgs) + { + var tokens = paneArgs.split("/"); + for(var i=0; i<tokens.length; i++) { - var args = {}; - args.action = paneAction; - args.rawdatasource = yellow.page.rawDataSource; - args.rawdataedit = document.getElementById("yellow-pane-edit-page").value; - args.rawdataendofline = yellow.page.rawDataEndOfLine; - yellow.toolbox.submitForm(args); - } else { - this.hidePane(paneId); + var pair = tokens[i].split(/[:=]/); + if(!pair[0] || !pair[1]) continue; + args[pair[0]] = pair[1]; } + } + yellow.toolbox.submitForm(args); + }, + + // Process help + processHelp: function() + { + this.hidePane(this.paneId); + window.open(this.getText("HelpUrl", "yellow"), "_self"); + }, + + // Process toolbar + processToolbar: function(status, args) + { + if(yellow.config.debug) console.log("yellow.edit.processToolbar status:"+status); + var elementText = document.getElementById("yellow-pane-edit-text"); + var elementPreview = document.getElementById("yellow-pane-edit-preview"); + if(!yellow.config.userRestrictions && this.paneAction!="delete" && !yellow.toolbox.isVisible(elementPreview)) + { + switch(status) + { + case "h1": yellow.editor.setMarkdown(elementText, "# ", "insert-multiline-block", true); break; + case "h2": yellow.editor.setMarkdown(elementText, "## ", "insert-multiline-block", true); break; + case "h3": yellow.editor.setMarkdown(elementText, "### ", "insert-multiline-block", true); break; + case "paragraph": yellow.editor.setMarkdown(elementText, "", "remove-multiline-block"); + yellow.editor.setMarkdown(elementText, "", "remove-fenced-block"); break; + case "quote": yellow.editor.setMarkdown(elementText, "> ", "insert-multiline-block", true); break; + case "pre": yellow.editor.setMarkdown(elementText, "```\n", "insert-fenced-block", true); break; + case "bold": yellow.editor.setMarkdown(elementText, "**", "insert-inline", true); break; + case "italic": yellow.editor.setMarkdown(elementText, "*", "insert-inline", true); break; + case "strikethrough": yellow.editor.setMarkdown(elementText, "~", "insert-inline", true); break; + case "code": yellow.editor.setMarkdown(elementText, "`", "insert-autodetect", true); break; + case "ul": yellow.editor.setMarkdown(elementText, "* ", "insert-multiline-block", true); break; + 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 "undo": yellow.editor.undo(); break; + case "redo": yellow.editor.redo(); break; + } + } + if(status=="preview") yellow.editor.showPreview(elementText, elementPreview); + if(status=="help") window.open(this.getText("HelpUrl", "yellow"), "_blank"); + if(status=="markdown") window.open(this.getText("MarkdownUrl", "yellow"), "_blank"); + if(status=="format" || status=="heading" || status=="list" || status=="emojiawesome" || status=="fontawesome") + { + this.showPopup("yellow-popup-"+status, status); + } else { + this.hidePopup(this.popupId); + } + }, + + // Update toolbar + updateToolbar: function(status) + { + if(status) + { + yellow.toolbox.addClass(document.getElementById("yellow-toolbar-"+status), "yellow-toolbar-selected"); } else { - var args = {"action":paneAction}; - if(paneArgs) + var elements = document.getElementsByClassName("yellow-toolbar-selected"); + for(var i=0, l=elements.length; i<l; i++) { - var tokens = paneArgs.split("/"); - for(var i=0; i<tokens.length; i++) + yellow.toolbox.removeClass(elements[i], "yellow-toolbar-selected"); + } + } + }, + + // Create popup + createPopup: function(popupId) + { + if(yellow.config.debug) console.log("yellow.edit.createPopup id:"+popupId); + var elementPopup = document.createElement("div"); + elementPopup.className = "yellow-popup"; + elementPopup.setAttribute("id", popupId); + elementPopup.style.display = "none"; + var elementDiv = document.createElement("div"); + elementDiv.setAttribute("id", popupId+"-content"); + switch(popupId) + { + case "yellow-popup-format": + elementDiv.innerHTML = + "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+ + "<li><a href=\"#\" id=\"yellow-popup-format-h1\" data-action=\"toolbar\" data-status=\"h1\">"+this.getText("ToolbarH1")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-format-h2\" data-action=\"toolbar\" data-status=\"h2\">"+this.getText("ToolbarH2")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-format-h3\" data-action=\"toolbar\" data-status=\"h3\">"+this.getText("ToolbarH3")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-format-paragraph\" data-action=\"toolbar\" data-status=\"paragraph\">"+this.getText("ToolbarParagraph")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-format-pre\" data-action=\"toolbar\" data-status=\"pre\">"+this.getText("ToolbarPre")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-format-quote\" data-action=\"toolbar\" data-status=\"quote\">"+this.getText("ToolbarQuote")+"</a></li>"+ + "</ul>"; + break; + case "yellow-popup-heading": + elementDiv.innerHTML = + "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+ + "<li><a href=\"#\" id=\"yellow-popup-heading-h1\" data-action=\"toolbar\" data-status=\"h1\">"+this.getText("ToolbarH1")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-heading-h2\" data-action=\"toolbar\" data-status=\"h2\">"+this.getText("ToolbarH2")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-heading-h3\" data-action=\"toolbar\" data-status=\"h3\">"+this.getText("ToolbarH3")+"</a></li>"+ + "</ul>"; + break; + case "yellow-popup-list": + elementDiv.innerHTML = + "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+ + "<li><a href=\"#\" id=\"yellow-popup-list-ul\" data-action=\"toolbar\" data-status=\"ul\">"+this.getText("ToolbarUl")+"</a></li>"+ + "<li><a href=\"#\" id=\"yellow-popup-list-ol\" data-action=\"toolbar\" data-status=\"ol\">"+this.getText("ToolbarOl")+"</a></li>"+ + "</ul>"; + break; + case "yellow-popup-emojiawesome": + var rawDataEmojis = ""; + if(yellow.config.emojiawesomeToolbarButtons && yellow.config.emojiawesomeToolbarButtons!="none") { - var pair = tokens[i].split(/[:=]/); - if(!pair[0] || !pair[1]) continue; - args[pair[0]] = pair[1]; + var tokens = yellow.config.emojiawesomeToolbarButtons.split(" "); + for(var i=0; i<tokens.length; i++) + { + var token = tokens[i].replace(/[\:]/g,""); + var className = token.replace("+1", "plus1").replace("-1", "minus1").replace(/_/g, "-"); + rawDataEmojis += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-args=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"ea ea-"+yellow.toolbox.encodeHtml(className)+"\"></i></a></li>"; + } } - } - yellow.toolbox.submitForm(args); + elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataEmojis+"</ul>"; + break; + case "yellow-popup-fontawesome": + var rawDataIcons = ""; + if(yellow.config.fontawesomeToolbarButtons && yellow.config.fontawesomeToolbarButtons!="none") + { + var tokens = yellow.config.fontawesomeToolbarButtons.split(" "); + for(var i=0; i<tokens.length; i++) + { + var token = tokens[i].replace(/[\:]/g,""); + rawDataIcons += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-args=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"fa "+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>"; + } + } + elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataIcons+"</ul>"; + break; + } + elementPopup.appendChild(elementDiv); + yellow.toolbox.insertAfter(elementPopup, document.getElementsByTagName("body")[0].firstChild); + this.bindActions(elementPopup); + }, + + // Show or hide popup + showPopup: function(popupId, status) + { + if(this.popupId!=popupId) + { + this.hidePopup(this.popupId); + if(!document.getElementById(popupId)) this.createPopup(popupId); + var element = document.getElementById(popupId); + if(yellow.config.debug) console.log("yellow.edit.showPopup id:"+popupId); + yellow.toolbox.setVisible(element, true); + this.popupId = popupId; + this.updateToolbar(status); + var elementParent = document.getElementById("yellow-toolbar-"+status); + var popupLeft = yellow.toolbox.getOuterLeft(elementParent); + var popupTop = yellow.toolbox.getOuterTop(elementParent) + yellow.toolbox.getOuterHeight(elementParent) - 1; + yellow.toolbox.setOuterLeft(document.getElementById(popupId), popupLeft); + yellow.toolbox.setOuterTop(document.getElementById(popupId), popupTop); + } else { + this.hidePopup(this.popupId, true); + } + }, + + // Hide popup + hidePopup: function(popupId, fadeout) + { + var element = document.getElementById(popupId); + if(yellow.toolbox.isVisible(element)) + { + yellow.toolbox.setVisible(element, false, fadeout); + this.popupId = 0; + this.updateToolbar(0); } }, @@ -482,6 +691,7 @@ yellow.edit = for(var i=0, l=elements.length; i<l; i++) { if(elements[i].getAttribute("data-action")) elements[i].onclick = yellow.onClickAction; + if(elements[i].getAttribute("data-action")=="toolbar") elements[i].onmousedown = function(e) { e.preventDefault() }; } }, @@ -494,11 +704,10 @@ yellow.edit = switch(paneAction) { case "create": action = "create"; break; - case "edit": action = document.getElementById("yellow-pane-edit-page").value ? "edit" : "delete"; break; + case "edit": action = document.getElementById("yellow-pane-edit-text").value.length!=0 ? "edit" : "delete"; break; case "delete": action = "delete"; break; } if(yellow.page.statusCode==434 && paneAction!="delete") action = "create"; - if(yellow.config.userRestrictions) action = ""; } return action; }, @@ -507,7 +716,7 @@ yellow.edit = getRequest: function(key, prefix) { if(!prefix) prefix = "request"; - key = prefix + key.charAt(0).toUpperCase() + key.slice(1); + key = prefix + yellow.toolbox.toUpperFirst(key); return (key in yellow.page) ? yellow.page[key] : ""; }, @@ -516,8 +725,305 @@ yellow.edit = { if(!prefix) prefix = "edit"; if(!postfix) postfix = ""; - key = prefix + key.charAt(0).toUpperCase() + key.slice(1) + postfix.charAt(0).toUpperCase() + postfix.slice(1); + key = prefix + yellow.toolbox.toUpperFirst(key) + yellow.toolbox.toUpperFirst(postfix); return (key in yellow.text) ? yellow.text[key] : "["+key+"]"; + }, + + // Check if plugin exists + isPlugin: function(name) + { + return name in yellow.config.serverPlugins; + } +}; + +yellow.editor = +{ + // Set Markdown formatting + setMarkdown: function(element, prefix, type, toggle, callback) + { + var information = this.getMarkdownInformation(element, prefix, type); + var selectionStart = (information.type.indexOf("block")!=-1) ? information.top : information.start; + var selectionEnd = (information.type.indexOf("block")!=-1) ? information.bottom : information.end; + if(information.found && toggle) information.type = information.type.replace("insert", "remove"); + if(information.type=="remove-fenced-block" || information.type=="remove-inline") + { + selectionStart -= information.prefix.length; selectionEnd += information.prefix.length; + } + var text = information.text; + var textSelectionBefore = text.substring(0, selectionStart); + var textSelection = text.substring(selectionStart, selectionEnd); + var textSelectionAfter = text.substring(selectionEnd, text.length); + var textSelectionNew, selectionStartNew, selectionEndNew; + switch(information.type) + { + case "insert-multiline-block": + textSelectionNew = this.getMarkdownMultilineBlock(textSelection, information); + selectionStartNew = information.start + this.getMarkdownDifference(textSelection, textSelectionNew, true); + selectionEndNew = information.end + this.getMarkdownDifference(textSelection, textSelectionNew); + if(information.start==information.top && information.start!=information.end) selectionStartNew = information.top; + if(information.end==information.top && information.start!=information.end) selectionEndNew = information.top; + break; + case "remove-multiline-block": + textSelectionNew = this.getMarkdownMultilineBlock(textSelection, information); + selectionStartNew = information.start + this.getMarkdownDifference(textSelection, textSelectionNew, true); + selectionEndNew = information.end + this.getMarkdownDifference(textSelection, textSelectionNew); + if(selectionStartNew<=information.top) selectionStartNew = information.top; + if(selectionEndNew<=information.top) selectionEndNew = information.top; + break; + case "insert-fenced-block": + textSelectionNew = this.getMarkdownFencedBlock(textSelection, information); + selectionStartNew = information.start + information.prefix.length; + selectionEndNew = information.end + this.getMarkdownDifference(textSelection, textSelectionNew) - information.prefix.length; + break; + case "remove-fenced-block": + textSelectionNew = this.getMarkdownFencedBlock(textSelection, information); + selectionStartNew = information.start - information.prefix.length; + selectionEndNew = information.end + this.getMarkdownDifference(textSelection, textSelectionNew) + information.prefix.length; + break; + case "insert-inline": + textSelectionNew = information.prefix + textSelection + information.prefix; + selectionStartNew = information.start + information.prefix.length; + selectionEndNew = information.end + information.prefix.length; + break; + case "remove-inline": + textSelectionNew = text.substring(information.start, information.end); + selectionStartNew = information.start - information.prefix.length; + selectionEndNew = information.end - information.prefix.length; + break; + case "insert": + textSelectionNew = callback ? callback(textSelection, information) : information.prefix; + selectionStartNew = information.start + textSelectionNew.length; + selectionEndNew = selectionStartNew; + } + if(textSelection!=textSelectionNew || selectionStart!=selectionStartNew || selectionEnd!=selectionEndNew) + { + element.focus(); + element.setSelectionRange(selectionStart, selectionEnd); + document.execCommand("insertText", false, textSelectionNew); + element.value = textSelectionBefore + textSelectionNew + textSelectionAfter; + element.setSelectionRange(selectionStartNew, selectionEndNew); + } + if(yellow.config.debug) console.log("yellow.editor.setMarkdown type:"+information.type); + }, + + // Return Markdown formatting information + getMarkdownInformation: function(element, prefix, type) + { + var text = element.value; + var start = element.selectionStart; + var end = element.selectionEnd; + var top = start, bottom = end; + while(text.charAt(top-1)!="\n" && top>0) top--; + if(bottom==top && bottom<text.length) bottom++; + while(text.charAt(bottom-1)!="\n" && bottom<text.length) bottom++; + if(type=="insert-autodetect") + { + if(text.substring(start, end).indexOf("\n")!=-1) + { + type = "insert-fenced-block"; prefix = "```\n"; + } else { + type = "insert-inline"; prefix = "`"; + } + } + var found = false; + if(type.indexOf("multiline-block")!=-1) + { + if(text.substring(top, top+prefix.length)==prefix) found = true; + } else if(type.indexOf("fenced-block")!=-1) { + if(text.substring(top-prefix.length, top)==prefix && text.substring(bottom, bottom+prefix.length)==prefix) + { + found = true; + } + } else { + if(text.substring(start-prefix.length, start)==prefix && text.substring(end, end+prefix.length)==prefix) + { + if(prefix=="*") + { + var lettersBefore = 0, lettersAfter = 0; + for(var index=start-1; text.charAt(index)=="*"; index--) lettersBefore++; + for(var index=end; text.charAt(index)=="*"; index++) lettersAfter++; + found = lettersBefore!=2 && lettersAfter!=2; + } else { + found = true; + } + } + } + return { "text":text, "prefix":prefix, "type":type, "start":start, "end":end, "top":top, "bottom":bottom, "found":found }; + }, + + // Return Markdown length difference + getMarkdownDifference: function(textSelection, textSelectionNew, firstTextLine) + { + var textSelectionLength, textSelectionLengthNew; + if(firstTextLine) + { + var position = textSelection.indexOf("\n"); + var positionNew = textSelectionNew.indexOf("\n"); + textSelectionLength = position!=-1 ? position+1 : textSelection.length+1; + textSelectionLengthNew = positionNew!=-1 ? positionNew+1 : textSelectionNew.length+1; + } else { + var position = textSelection.indexOf("\n"); + var positionNew = textSelectionNew.indexOf("\n"); + textSelectionLength = position!=-1 ? textSelection.length : textSelection.length+1; + textSelectionLengthNew = positionNew!=-1 ? textSelectionNew.length : textSelectionNew.length+1; + } + return textSelectionLengthNew - textSelectionLength; + }, + + // Return Markdown for multiline block + getMarkdownMultilineBlock: function(textSelection, information) + { + var textSelectionNew = ""; + var lines = yellow.toolbox.getTextLines(textSelection); + for(var i=0; i<lines.length; i++) + { + var matches = lines[i].match(/^(\s*[\#\*\-\>\s]+)?(\s+\[.\]|\s*\d+\.)?[ \t]+/); + if(matches) + { + textSelectionNew += lines[i].substring(matches[0].length); + } else { + textSelectionNew += lines[i]; + } + } + textSelection = textSelectionNew; + if(information.type.indexOf("remove")==-1) + { + textSelectionNew = ""; + var linePrefix = information.prefix; + lines = yellow.toolbox.getTextLines(textSelection.length!=0 ? textSelection : "\n"); + for(var i=0; i<lines.length; i++) + { + textSelectionNew += linePrefix+lines[i]; + if(information.prefix=="1. ") + { + var matches = linePrefix.match(/^(\d+)\.\s/); + if(matches) linePrefix = (parseInt(matches[1])+1)+". "; + } + } + textSelection = textSelectionNew; + } + return textSelection; + }, + + // Return Markdown for fenced block + getMarkdownFencedBlock: function(textSelection, information) + { + var textSelectionNew = ""; + var lines = yellow.toolbox.getTextLines(textSelection); + for(var i=0; i<lines.length; i++) + { + var matches = lines[i].match(/^```/); + if(!matches) textSelectionNew += lines[i]; + } + textSelection = textSelectionNew; + if(information.type.indexOf("remove")==-1) + { + if(textSelection.length==0) textSelection = "\n"; + textSelection = information.prefix + textSelection + information.prefix; + } + return textSelection; + }, + + // Return Markdown for link + getMarkdownLink: function(textSelection, information) + { + return textSelection.length!=0 ? information.prefix.replace("link", textSelection) : information.prefix; + }, + + // Set meta data + setMetaData: function(element, key, value, toggle) + { + var information = this.getMetaDataInformation(element, key); + if(information.bottom!=0) + { + var selectionStart = information.found ? information.start : information.bottom; + var selectionEnd = information.found ? information.end : information.bottom; + var text = information.text; + var textSelectionBefore = text.substring(0, selectionStart); + var textSelection = text.substring(selectionStart, selectionEnd); + var textSelectionAfter = text.substring(selectionEnd, text.length); + var textSelectionNew = yellow.toolbox.toUpperFirst(key)+": "+value+"\n"; + if(information.found && information.value==value && toggle) textSelectionNew = ""; + var selectionStartNew = selectionStart; + var selectionEndNew = selectionStart + textSelectionNew.trim().length; + element.focus(); + element.setSelectionRange(selectionStart, selectionEnd); + document.execCommand("insertText", false, textSelectionNew); + element.value = textSelectionBefore + textSelectionNew + textSelectionAfter; + element.setSelectionRange(selectionStartNew, selectionEndNew); + element.scrollTop = 0; + if(yellow.config.debug) console.log("yellow.editor.setMetaData key:"+key); + } + }, + + // Return meta data information + getMetaDataInformation: function(element, key) + { + var text = element.value; + var value = ""; + var start = 0, end = 0, top = 0, bottom = 0; + var found = false; + var parts = text.match(/^(\xEF\xBB\xBF)?(\-\-\-[\r\n]+)([\s\S]+?)\-\-\-[\r\n]+/); + if(parts) + { + key = yellow.toolbox.toLowerFirst(key); + start = end = top = ((parts[1] ? parts[1] : "")+parts[2]).length; + bottom = ((parts[1] ? parts[1] : "")+parts[2]+parts[3]).length; + var lines = yellow.toolbox.getTextLines(parts[3]); + for(var i=0; i<lines.length; i++) + { + var matches = lines[i].match(/^\s*(.*?)\s*:\s*(.*?)\s*$/); + if(matches && yellow.toolbox.toLowerFirst(matches[1])==key && matches[2].length!=0) + { + value = matches[2]; + end = start + lines[i].length; + found = true; + break; + } + start = end = start + lines[i].length; + } + } + return { "text":text, "value":value, "start":start, "end":end, "top":top, "bottom":bottom, "found":found }; + }, + + // Show or hide preview + showPreview: function(elementText, elementPreview) + { + if(!yellow.toolbox.isVisible(elementPreview)) + { + var formData = new FormData(); + formData.append("action", "preview"); + formData.append("rawdataedit", elementText.value); + formData.append("rawdataendofline", yellow.page.rawDataEndOfLine); + var request = new XMLHttpRequest(); + request.open("POST", window.location.pathname, true); + request.onload = function() { if(this.status==200) yellow.editor.updatePreview(elementText, elementPreview, this.responseText); }; + request.send(formData); + } else { + yellow.toolbox.setVisible(elementText, true); + yellow.toolbox.setVisible(elementPreview, false); + elementText.focus(); + } + }, + + // Update preview + updatePreview: function(elementText, elementPreview, responseText) + { + yellow.toolbox.setVisible(elementText, false); + yellow.toolbox.setVisible(elementPreview, true); + elementPreview.innerHTML = responseText; + }, + + // Undo changes + undo: function() + { + document.execCommand("undo"); + }, + + // Redo changes + redo: function() + { + document.execCommand("redo"); } }; @@ -683,29 +1189,55 @@ yellow.toolbox = return { "width":width, "height":height }; }, - // Set input cursor position - setCursorPosition: function(element, pos) + // Set element visibility + setVisible: function(element, show, fadeout) { - element.focus(); - element.setSelectionRange(pos, pos); + if(fadeout && !show) + { + var opacity = 1; + function renderFrame() + { + opacity -= .1; + if(opacity<=0) + { + element.style.opacity = "initial"; + element.style.display = "none"; + } else { + element.style.opacity = opacity; + requestAnimationFrame(renderFrame); + } + } + renderFrame(); + } else { + element.style.display = show ? "block" : "none"; + } }, - // Get input cursor position - getCursorPosition: function(element) + // Check if element exists and is visible + isVisible: function(element) { - return element.selectionStart; + return element && element.style.display!="none"; }, - // Set element visibility - setVisible: function(element, show) + // Convert first letter to lowercase + toLowerFirst: function(string) { - element.style.display = show ? "block" : "none"; + return string.charAt(0).toLowerCase()+string.slice(1); }, - // Check if element exists and is visible - isVisible: function(element) + // Convert first letter to uppercase + toUpperFirst: function(string) { - return element && element.style.display!="none"; + return string.charAt(0).toUpperCase()+string.slice(1); + }, + + // Return lines from text string, including newline + getTextLines: function(string) + { + var lines = string.split("\n"); + for(var i=0; i<lines.length; i++) lines[i] = lines[i]+"\n"; + if(string.length==0 || string.charAt(string.length-1)=="\n") lines.pop(); + return lines; }, // Encode HTML special characters diff --git a/system/plugins/edit.php b/system/plugins/edit.php @@ -5,7 +5,7 @@ class YellowEdit { - const VERSION = "0.7.9"; + const VERSION = "0.7.10"; var $yellow; //access to API var $response; //web response var $users; //user accounts @@ -19,6 +19,7 @@ class YellowEdit $this->users = new YellowUsers($yellow); $this->merge = new YellowMerge($yellow); $this->yellow->config->setDefault("editLocation", "/edit/"); + $this->yellow->config->setDefault("editToolbarButtons", "auto"); $this->yellow->config->setDefault("editEndOfLine", "auto"); $this->yellow->config->setDefault("editUserFile", "user.ini"); $this->yellow->config->setDefault("editUserPasswordMinLength", "4"); @@ -205,6 +206,7 @@ class YellowEdit case "create": $statusCode = $this->processRequestCreate($scheme, $address, $base, $location, $fileName); break; 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; } } else { $this->yellow->lookup->requestHandler = "core"; @@ -695,6 +697,20 @@ class YellowEdit } return $statusCode; } + + // Process request to show preview + function processRequestPreview($scheme, $address, $base, $location, $fileName) + { + $page = $this->response->getPagePreview($scheme, $address, $base, $location, $fileName, + $_REQUEST["rawdataedit"], $_REQUEST["rawdataendofline"]); + $statusCode = $this->yellow->sendData(200, $page->outputData, "", false); + if(defined("DEBUG") && DEBUG>=1) + { + $parser = $page->get("parser"); + echo "YellowEdit::processRequestPreview parser:$parser<br/>\n"; + } + return $statusCode; + } // Check request function checkRequest($location) @@ -928,7 +944,27 @@ class YellowResponse } return $page; } - + + // Return preview page + function getPagePreview($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) + { + $page = new YellowPage($this->yellow); + $page->setRequestInformation($scheme, $address, $base, $location, $fileName); + $page->parseData($this->normaliseLines($rawData, $endOfLine), false, 200); + $this->yellow->text->setLanguage($page->get("language")); + $page->set("pageClass", "page-preview"); + $page->set("pageClass", $page->get("pageClass")." template-".$page->get("template")); + $output = "<div class=\"".$page->getHtml("pageClass")."\"><div class=\"content\">"; + if($this->yellow->config->get("editToolbarButtons")!="none") + { + $output .= "<h1>".$page->getHtml("titleContent")."</h1>\n"; + } + $output .= $page->getContent(); + $output .= "</div></div>"; + $page->setOutput($output); + return $page; + } + // Return page data including login information function getPageData() { @@ -967,16 +1003,23 @@ class YellowResponse $data["userHome"] = $this->plugin->users->getHome($this->userEmail); $data["userRestrictions"] = intval($this->isUserRestrictions()); $data["userWebmaster"] = intval($this->isUserWebmaster()); - $data["userUpdate"] = intval($this->yellow->plugins->isExisting("update")); + $data["serverScheme"] = $this->yellow->config->get("serverScheme"); + $data["serverAddress"] = $this->yellow->config->get("serverAddress"); + $data["serverBase"] = $this->yellow->config->get("serverBase"); + $data["serverVersion"] = "Datenstrom Yellow ".YellowCore::VERSION; + $data["serverPlugins"] = array(); + foreach($this->yellow->plugins->plugins as $key=>$value) + { + $data["serverPlugins"][$key] = $value["plugin"]; + } $data["serverLanguages"] = array(); foreach($this->yellow->text->getLanguages() as $language) { $data["serverLanguages"][$language] = $this->yellow->text->getTextHtml("languageDescription", $language); } - $data["serverScheme"] = $this->yellow->config->get("serverScheme"); - $data["serverAddress"] = $this->yellow->config->get("serverAddress"); - $data["serverBase"] = $this->yellow->config->get("serverBase"); - $data["serverVersion"] = "Datenstrom Yellow ".YellowCore::VERSION; + $data["editToolbarButtons"] = $this->getToolbarButtons("edit"); + $data["emojiawesomeToolbarButtons"] = $this->getToolbarButtons("emojiawesome"); + $data["fontawesomeToolbarButtons"] = $this->getToolbarButtons("fontawesome"); } else { $data["editLoginEmail"] = $this->yellow->config->get("editLoginEmail"); $data["editLoginPassword"] = $this->yellow->config->get("editLoginPassword"); @@ -992,7 +1035,7 @@ class YellowResponse $data = array(); foreach($_REQUEST as $key=>$value) { - if($key=="login" || $key=="password") continue; + if($key=="login" || $key=="password" || substru($key, 0, 7)=="rawdata") continue; $data["request".ucfirst($key)] = trim($value); } return $data; @@ -1007,6 +1050,27 @@ class YellowResponse return array_merge($textLanguage, $textEdit, $textYellow); } + // Return toolbar buttons + function getToolbarButtons($name) + { + if($name=="edit") + { + $toolbarButtons = $this->yellow->config->get("editToolbarButtons"); + if($toolbarButtons=="auto") + { + $toolbarButtons = ""; + if($this->yellow->plugins->isExisting("markdown")) $toolbarButtons = "preview, format, bold, italic, code, list, link, file"; + if($this->yellow->plugins->isExisting("emojiawesome")) $toolbarButtons .= ", emojiawesome"; + if($this->yellow->plugins->isExisting("fontawesome")) $toolbarButtons .= ", fontawesome"; + if($this->yellow->plugins->isExisting("draft")) $toolbarButtons .= ", draft"; + if($this->yellow->plugins->isExisting("markdown")) $toolbarButtons .= ", markdown"; + } + } else { + $toolbarButtons = $this->yellow->config->get("{$name}ToolbarButtons"); + } + return $toolbarButtons; + } + // Return end of line format function getEndOfLine($rawData = "") { diff --git a/system/plugins/edit.woff b/system/plugins/edit.woff Binary files differ. diff --git a/system/plugins/language-de.txt b/system/plugins/language-de.txt @@ -3,7 +3,7 @@ Language: de LanguageDescription: Deutsch LanguageTranslator: David Fehrmann -LanguageVersion: 0.7.4 +LanguageVersion: 0.7.5 BlogBy: von BlogFilter: Blog: @@ -101,9 +101,32 @@ EditDeleteButton: Löschen EditEdit: Seite bearbeiten EditCreate: + EditDelete: - -EditCreateTitle: Seite erzeugen -EditDeleteTitle: Seite löschen -EditMarkdownHelp: Markdown +EditToolbarPreview: Vorschau +EditToolbarFormat: Format +EditToolbarHeading: Überschrift +EditToolbarH1: Überschrift 1 +EditToolbarH2: Überschrift 2 +EditToolbarH3: Überschrift 3 +EditToolbarParagraph: Normaler Text +EditToolbarPre: Quellcode +EditToolbarQuote: Zitat +EditToolbarBold: Fettschrift +EditToolbarItalic: Kursiv +EditToolbarStrikethrough: Durchgestrichen +EditToolbarCode: Code +EditToolbarList: Liste +EditToolbarUl: • Unsortierte Liste +EditToolbarOl: 1. Sortierte Liste +EditToolbarTl: ✓ Aufgabenliste +EditToolbarLink: Link +EditToolbarFile: Datei +EditToolbarEmojiawesome: Emoji +EditToolbarFontawesome: Symbol +EditToolbarDraft: Entwurf +EditToolbarUndo: Rückgängig +EditToolbarRedo: Wiederholen +EditToolbarMarkdown: Markdown +EditToolbarHelp: Hilfe EditUserHelp: Hilfe EditUserLogout: Abmelden PagePrevious: ← Zurück: @title @@ -123,5 +146,5 @@ WikiTag: Tags: WikiSpecialPages: Alle Seiten WikiSpecialChanges: Letzte Änderungen YellowUrl: https://datenstrom.se/de/yellow/ -YellowUserHelpUrl: https://developers.datenstrom.se/de/help/ -YellowMarkdownHelpUrl: https://developers.datenstrom.se/de/help/markdown-cheat-sheet +YellowHelpUrl: https://developers.datenstrom.se/de/help/ +YellowMarkdownUrl: https://developers.datenstrom.se/de/help/markdown-cheat-sheet diff --git a/system/plugins/language-en.txt b/system/plugins/language-en.txt @@ -3,7 +3,7 @@ Language: en LanguageDescription: English LanguageTranslator: Mark Seuffert -LanguageVersion: 0.7.4 +LanguageVersion: 0.7.5 BlogBy: by BlogFilter: Blog: @@ -101,9 +101,32 @@ EditDeleteButton: Delete EditEdit: Edit page EditCreate: + EditDelete: - -EditCreateTitle: Create page -EditDeleteTitle: Delete page -EditMarkdownHelp: Markdown +EditToolbarPreview: Preview +EditToolbarFormat: Format +EditToolbarHeading: Heading +EditToolbarH1: Heading 1 +EditToolbarH2: Heading 2 +EditToolbarH3: Heading 3 +EditToolbarParagraph: Normal text +EditToolbarPre: Source code +EditToolbarQuote: Quote +EditToolbarBold: Bold +EditToolbarItalic: Italic +EditToolbarStrikethrough: Strikethrough +EditToolbarCode: Code +EditToolbarList: List +EditToolbarUl: • Unordered list +EditToolbarOl: 1. Ordered list +EditToolbarTl: ✓ Task list +EditToolbarLink: Link +EditToolbarFile: File +EditToolbarEmojiawesome: Emoji +EditToolbarFontawesome: Icon +EditToolbarDraft: Draft +EditToolbarUndo: Undo +EditToolbarRedo: Redo +EditToolbarMarkdown: Markdown +EditToolbarHelp: Help EditUserHelp: Help EditUserLogout: Logout PagePrevious: ← Previous: @title @@ -123,5 +146,5 @@ WikiTag: Tags: WikiSpecialPages: All pages WikiSpecialChanges: Recent changes YellowUrl: https://datenstrom.se/yellow/ -YellowUserHelpUrl: https://developers.datenstrom.se/help/ -YellowMarkdownHelpUrl: https://developers.datenstrom.se/help/markdown-cheat-sheet +YellowHelpUrl: https://developers.datenstrom.se/help/ +YellowMarkdownUrl: https://developers.datenstrom.se/help/markdown-cheat-sheet diff --git a/system/plugins/language-fr.txt b/system/plugins/language-fr.txt @@ -3,7 +3,7 @@ Language: fr LanguageDescription: Français LanguageTranslator: Juh Nibreh -LanguageVersion: 0.7.4 +LanguageVersion: 0.7.5 BlogBy: par BlogFilter: Blog: @@ -101,9 +101,32 @@ EditDeleteButton: Supprimer EditEdit: Éditer page EditCreate: + EditDelete: - -EditCreateTitle: Créer page -EditDeleteTitle: Supprimer page -EditMarkdownHelp: Markdown +EditToolbarPreview: Aperçu +EditToolbarFormat: Format +EditToolbarHeading: Titre +EditToolbarH1: Titre 1 +EditToolbarH2: Titre 2 +EditToolbarH3: Titre 3 +EditToolbarParagraph: Texte normal +EditToolbarPre: Code source +EditToolbarQuote: Citation +EditToolbarBold: Gras +EditToolbarItalic: Italique +EditToolbarStrikethrough: Barré +EditToolbarCode: Code +EditToolbarList: Liste +EditToolbarUl: • Liste non ordonnée +EditToolbarOl: 1. Liste commandée +EditToolbarTl: ✓ Liste des tâches +EditToolbarLink: Lien +EditToolbarFile: Fichier +EditToolbarEmojiawesome: Emoji +EditToolbarFontawesome: Icone +EditToolbarDraft: Brouillon +EditToolbarUndo: Annuler +EditToolbarRedo: Refaire +EditToolbarMarkdown: Markdown +EditToolbarHelp: Aide EditUserHelp: Aide EditUserLogout: Déconnexion PagePrevious: ← Précédent: @title @@ -123,5 +146,5 @@ WikiTag: Tags: WikiSpecialPages: Toutes les pages WikiSpecialChanges: Changements récents YellowUrl: https://datenstrom.se/fr/yellow/ -YellowUserHelpUrl: https://developers.datenstrom.se/fr/help/ -YellowMarkdownHelpUrl: https://developers.datenstrom.se/fr/help/markdown-cheat-sheet +YellowHelpUrl: https://developers.datenstrom.se/fr/help/ +YellowMarkdownUrl: https://developers.datenstrom.se/fr/help/markdown-cheat-sheet diff --git a/system/plugins/language.php b/system/plugins/language.php @@ -5,7 +5,7 @@ class YellowLanguage { - const VERSION = "0.7.4"; + const VERSION = "0.7.5"; } $yellow->plugins->register("language", "YellowLanguage", YellowLanguage::VERSION); diff --git a/system/themes/assets/flatsite.css b/system/themes/assets/flatsite.css @@ -1,5 +1,5 @@ /* Flatsite theme, https://github.com/datenstrom/yellow-themes/tree/master/flatsite */ -/* Copyright (c) 2013-2017 Datenstrom, https://datenstrom.se */ +/* Copyright (c) 2013-2018 Datenstrom, https://datenstrom.se */ /* This file may be used and distributed under the terms of the public license. */ html, body, div, form, pre, span, tr, th, td, img { margin:0; padding:0; border:0; vertical-align:baseline; } @@ -29,7 +29,7 @@ body { font-weight:300; line-height:1.5; } -h1, h2, h3, h4, h5, h6 { color:#07d; font-weight:normal; } +h1, h2, h3, h4, h5, h6 { color:#111; font-weight:normal; } h1 { font-size:2.0em; } hr { height:1px; background:#ddd; border:0; } strong { font-weight:bold; } @@ -37,7 +37,8 @@ code { font-size:1.1em; } a { color:#07d; text-decoration:none; } a:hover { color:#07d; text-decoration:underline; } .content h1:first-child, .content>*:first-child { margin-top:0; } -.content h1 a:hover { text-decoration:none; } +.content h1 a { color:#111; } +.content h1 a:hover { color:#111; text-decoration:none; } .content img { max-width:100%; height:auto; } .content form { margin:1em 0; } .content table { border-spacing:0; border-collapse:collapse; } diff --git a/system/themes/assets/flatsite.php b/system/themes/assets/flatsite.php @@ -1,11 +1,11 @@ <?php // Flatsite theme, https://github.com/datenstrom/yellow-themes/tree/master/flatsite -// Copyright (c) 2013-2017 Datenstrom, https://datenstrom.se +// Copyright (c) 2013-2018 Datenstrom, https://datenstrom.se // This file may be used and distributed under the terms of the public license. class YellowThemeFlatsite { - const VERSION = "0.7.2"; + const VERSION = "0.7.3"; } $yellow->themes->register("flatsite", "YellowThemeFlatsite", YellowThemeFlatsite::VERSION);