mikuli.cz

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

edit.js (87110B)


      1 // Edit extension, https://github.com/annaesvensson/yellow-edit
      2 
      3 var yellow = {
      4     onLoad: function(e) { yellow.edit.load(e); },
      5     onKeydown: function(e) { yellow.edit.keydown(e); },
      6     onDrag: function(e) { yellow.edit.drag(e); },
      7     onDrop: function(e) { yellow.edit.drop(e); },
      8     onClick: function(e) { yellow.edit.click(e); },
      9     onClickAction: function(e) { yellow.edit.clickAction(e); },
     10     onPageShow: function(e) { yellow.edit.pageShow(e); },
     11     onUpdatePane: function() { yellow.edit.updatePane(yellow.edit.paneId, yellow.edit.paneAction, yellow.edit.paneStatus); },
     12     onResizePane: function() { yellow.edit.resizePane(yellow.edit.paneId, yellow.edit.paneAction, yellow.edit.paneStatus); },
     13     action: function(action, status, arguments) { yellow.edit.processAction(action, status, arguments); }
     14 };
     15 
     16 yellow.edit = {
     17     paneId: 0,          // visible pane ID
     18     paneAction: 0,      // current pane action
     19     paneStatus: 0,      // current pane status
     20     popupId: 0,         // visible popup ID
     21     intervalId: 0,      // timer interval ID
     22 
     23     // Handle initialisation
     24     load: function(e) {
     25         var body = document.getElementsByTagName("body")[0];
     26         if (body && body.firstChild && !document.getElementById("yellow-bar")) {
     27             this.createBar("yellow-bar");
     28             this.processAction(yellow.page.action, yellow.page.status);
     29             clearInterval(this.intervalId);
     30         }
     31         if (e.type=="DOMContentLoaded") {
     32             var page = document.getElementsByClassName("page")[0];
     33             if (page) this.bindActions(page);
     34         }
     35     },
     36     
     37     // Handle keyboard
     38     keydown: function(e) {
     39         if (this.paneId=="yellow-pane-create" || this.paneId=="yellow-pane-edit" || this.paneId=="yellow-pane-delete") this.processShortcut(e);
     40         if (this.paneId && e.keyCode==27) this.hidePane(this.paneId);
     41     },
     42     
     43     // Handle drag
     44     drag: function(e) {
     45         e.stopPropagation();
     46         e.preventDefault();
     47     },
     48     
     49     // Handle drop
     50     drop: function(e) {
     51         e.stopPropagation();
     52         e.preventDefault();
     53         var elementText = document.getElementById(this.paneId+"-text");
     54         var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
     55         for (var i=0; i<files.length; i++) this.uploadFile(elementText, files[i]);
     56     },
     57     
     58     // Handle mouse clicked
     59     click: function(e) {
     60         if (this.popupId && !document.getElementById(this.popupId).contains(e.target)) this.hidePopup(this.popupId, true);
     61         if (this.paneId && !document.getElementById(this.paneId).contains(e.target)) this.hidePane(this.paneId, true);
     62     },
     63     
     64     // Handle action clicked
     65     clickAction: function(e) {
     66         e.stopPropagation();
     67         e.preventDefault();
     68         var element = e.target;
     69         for (; element; element=element.parentNode) {
     70             if (element.tagName=="A") break;
     71         }
     72         this.processAction(element.getAttribute("data-action"), element.getAttribute("data-status"), element.getAttribute("data-arguments"));
     73     },
     74     
     75     // Handle page cache
     76     pageShow: function(e) {
     77         if (e.persisted && yellow.user.email && !this.getCookie("yellowcsrftoken")) {
     78             window.location.reload();
     79         }
     80     },
     81     
     82     // Create bar
     83     createBar: function(barId) {
     84         var elementBar = document.createElement("div");
     85         elementBar.className = "yellow-bar";
     86         elementBar.setAttribute("id", barId);
     87         if (barId=="yellow-bar") {
     88             yellow.toolbox.addEvent(document, "click", yellow.onClick);
     89             yellow.toolbox.addEvent(document, "keydown", yellow.onKeydown);
     90             yellow.toolbox.addEvent(window, "pageshow", yellow.onPageShow);
     91             yellow.toolbox.addEvent(window, "resize", yellow.onResizePane);
     92         }
     93         var elementDiv = document.createElement("div");
     94         elementDiv.setAttribute("id", barId+"-content");
     95         if (yellow.user.name) {
     96             elementDiv.innerHTML =
     97                 "<div class=\"yellow-bar-left\">"+
     98                 this.getRawDataPaneAction("edit")+
     99                 "</div>"+
    100                 "<div class=\"yellow-bar-right\">"+
    101                 this.getRawDataPaneAction("create")+
    102                 this.getRawDataPaneAction("delete")+
    103                 this.getRawDataPaneAction("menu", yellow.user.name, true)+
    104                 "</div>"+
    105                 "<div class=\"yellow-bar-banner\"></div>";
    106         } else {
    107             elementDiv.innerHTML = "&nbsp;";
    108         }
    109         elementBar.appendChild(elementDiv);
    110         yellow.toolbox.insertBefore(elementBar, document.getElementsByTagName("body")[0].firstChild);
    111         this.bindActions(elementBar);
    112     },
    113     
    114     // Update bar
    115     updateBar: function(paneId, name) {
    116         if (paneId) {
    117             var element = document.getElementById(paneId+"-bar");
    118             if (element) {
    119                 if (name.indexOf("selected")!=-1) element.setAttribute("aria-expanded", "true");
    120                 yellow.toolbox.addClass(element, name);
    121             }
    122         } else {
    123             var elements = document.getElementsByClassName(name);
    124             for (var i=0, l=elements.length; i<l; i++) {
    125                 if (name.indexOf("selected")!=-1) elements[i].setAttribute("aria-expanded", "false");
    126                 yellow.toolbox.removeClass(elements[i], name);
    127             }
    128         }
    129     },
    130     
    131     // Create pane
    132     createPane: function(paneId, paneAction, paneStatus) {
    133         if (yellow.system.coreDebugMode) console.log("yellow.edit.createPane id:"+paneId);
    134         var elementPane = document.createElement("div");
    135         elementPane.className = "yellow-pane";
    136         elementPane.setAttribute("id", paneId);
    137         elementPane.style.display = "none";
    138         if (paneId=="yellow-pane-create" || paneId=="yellow-pane-edit") {
    139             yellow.toolbox.addEvent(elementPane, "input", yellow.onUpdatePane);
    140             yellow.toolbox.addEvent(elementPane, "dragenter", yellow.onDrag);
    141             yellow.toolbox.addEvent(elementPane, "dragover", yellow.onDrag);
    142             yellow.toolbox.addEvent(elementPane, "drop", yellow.onDrop);
    143         }
    144         if (paneId=="yellow-pane-create" || paneId=="yellow-pane-edit" || paneId=="yellow-pane-delete" || paneId=="yellow-pane-menu") {
    145             var elementArrow = document.createElement("span");
    146             elementArrow.className = "yellow-arrow";
    147             elementArrow.setAttribute("id", paneId+"-arrow");
    148             elementPane.appendChild(elementArrow);
    149         }
    150         var elementDiv = document.createElement("div");
    151         elementDiv.className = "yellow-content";
    152         elementDiv.setAttribute("id", paneId+"-content");
    153         switch (paneId) {
    154             case "yellow-pane-login":
    155                 elementDiv.innerHTML =
    156                 "<form method=\"post\">"+
    157                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    158                 "<div class=\"yellow-title\"><h1>"+this.getText("LoginTitle")+"</h1></div>"+
    159                 "<div class=\"yellow-fields\">"+
    160                 "<input type=\"hidden\" name=\"action\" value=\"login\" />"+
    161                 "<p><label for=\"yellow-pane-login-email\">"+this.getText("LoginEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-login-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(yellow.system.editLoginEmail)+"\" /></p>"+
    162                 "<p><label for=\"yellow-pane-login-password\">"+this.getText("LoginPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"yellow-pane-login-password\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(yellow.system.editLoginPassword)+"\" /></p>"+
    163                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+
    164                 "<p><a href=\"#\" id=\"yellow-pane-login-forgot\" class=\"yellow-center\" data-action=\"forgot\">"+this.getText("LoginForgot")+"</a><br /><a href=\"#\" id=\"yellow-pane-login-signup\" class=\"yellow-center\" data-action=\"signup\">"+this.getText("LoginSignup")+"</a></p>"+
    165                 "</div>"+
    166                 "</form>";
    167                 break;
    168             case "yellow-pane-signup":
    169                 elementDiv.innerHTML =
    170                 "<form method=\"post\">"+
    171                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    172                 "<div class=\"yellow-title\"><h1>"+this.getText("SignupTitle")+"</h1></div>"+
    173                 "<div class=\"yellow-status\"><p id=\"yellow-pane-signup-status\" class=\""+paneStatus+"\">"+this.getText("SignupStatus", "", paneStatus)+"</p></div>"+
    174                 "<div class=\"yellow-fields\">"+
    175                 "<input type=\"hidden\" name=\"action\" value=\"signup\" />"+
    176                 "<p><label for=\"yellow-pane-signup-name\">"+this.getText("SignupName")+"</label><br /><input class=\"yellow-form-control\" name=\"name\" id=\"yellow-pane-signup-name\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("name"))+"\" /></p>"+
    177                 "<p><label for=\"yellow-pane-signup-email\">"+this.getText("SignupEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-signup-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
    178                 "<p><label for=\"yellow-pane-signup-password\">"+this.getText("SignupPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"yellow-pane-signup-password\" maxlength=\"64\" value=\"\" /></p>"+
    179                 "<p><input type=\"checkbox\" name=\"consent\" value=\"consent\" id=\"yellow-pane-signup-consent\""+(this.getRequest("consent") ? " checked=\"checked\"" : "")+"> <label for=\"yellow-pane-signup-consent\">"+this.getText("SignupConsent")+"</label></p>"+
    180                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("SignupButton")+"\" /></p>"+
    181                 "</div>"+
    182                 "</form>";
    183                 break;
    184             case "yellow-pane-forgot":
    185                 elementDiv.innerHTML =
    186                 "<form method=\"post\">"+
    187                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    188                 "<div class=\"yellow-title\"><h1>"+this.getText("ForgotTitle")+"</h1></div>"+
    189                 "<div class=\"yellow-status\"><p id=\"yellow-pane-forgot-status\" class=\""+paneStatus+"\">"+this.getText("ForgotStatus", "", paneStatus)+"</p></div>"+
    190                 "<div class=\"yellow-fields\">"+
    191                 "<input type=\"hidden\" name=\"action\" value=\"forgot\" />"+
    192                 "<p><label for=\"yellow-pane-forgot-email\">"+this.getText("ForgotEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-forgot-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
    193                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("OkButton")+"\" /></p>"+
    194                 "</div>"+
    195                 "</form>";
    196                 break;
    197             case "yellow-pane-recover":
    198                 elementDiv.innerHTML =
    199                 "<form method=\"post\">"+
    200                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    201                 "<div class=\"yellow-title\"><h1>"+this.getText("RecoverTitle")+"</h1></div>"+
    202                 "<div class=\"yellow-status\"><p id=\"yellow-pane-recover-status\" class=\""+paneStatus+"\">"+this.getText("RecoverStatus", "", paneStatus)+"</p></div>"+
    203                 "<div class=\"yellow-fields\">"+
    204                 "<p><label for=\"yellow-pane-recover-password\">"+this.getText("RecoverPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"yellow-pane-recover-password\" maxlength=\"64\" value=\"\" /></p>"+
    205                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("OkButton")+"\" /></p>"+
    206                 "</div>"+
    207                 "</form>";
    208                 break;
    209             case "yellow-pane-quit":
    210                 elementDiv.innerHTML =
    211                 "<form method=\"post\">"+
    212                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    213                 "<div class=\"yellow-title\"><h1>"+this.getText("QuitTitle")+"</h1></div>"+
    214                 "<div class=\"yellow-status\"><p id=\"yellow-pane-quit-status\" class=\""+paneStatus+"\">"+this.getText("QuitStatus", "", paneStatus)+"</p></div>"+
    215                 "<div class=\"yellow-fields\">"+
    216                 "<input type=\"hidden\" name=\"action\" value=\"quit\" />"+
    217                 "<input type=\"hidden\" name=\"yellowcsrftoken\" value=\""+yellow.toolbox.encodeHtml(this.getCookie("yellowcsrftoken"))+"\" />"+
    218                 "<p><label for=\"yellow-pane-quit-name\">"+this.getText("SignupName")+"</label><br /><input class=\"yellow-form-control\" name=\"name\" id=\"yellow-pane-quit-name\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("name"))+"\" /></p>"+
    219                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("DeleteButton")+"\" /></p>"+
    220                 "</div>"+
    221                 "</form>";
    222                 break;
    223             case "yellow-pane-account":
    224                 elementDiv.innerHTML =
    225                 "<form method=\"post\">"+
    226                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    227                 "<div class=\"yellow-title\"><h1 id=\"yellow-pane-account-title\">"+this.getText("AccountTitle")+"</h1></div>"+
    228                 "<div class=\"yellow-status\"><p id=\"yellow-pane-account-status\" class=\""+paneStatus+"\">"+this.getText("AccountStatus", "", paneStatus)+"</p></div>"+
    229                 "<div class=\"yellow-settings\">"+
    230                 "<div id=\"yellow-pane-account-settings-actions\" class=\"yellow-settings-left\"><p>"+this.getRawDataSettingsActions(paneAction)+"</p></div>"+
    231                 "<div id=\"yellow-pane-account-settings-separator\" class=\"yellow-settings-left yellow-settings-separator\">&nbsp;</div>"+
    232                 "<div id=\"yellow-pane-account-settings-fields\" class=\"yellow-settings-right yellow-fields\">"+
    233                 "<input type=\"hidden\" name=\"action\" value=\"account\" />"+
    234                 "<input type=\"hidden\" name=\"yellowcsrftoken\" value=\""+yellow.toolbox.encodeHtml(this.getCookie("yellowcsrftoken"))+"\" />"+
    235                 "<p><label for=\"yellow-pane-account-name\">"+this.getText("SignupName")+"</label><br /><input class=\"yellow-form-control\" name=\"name\" id=\"yellow-pane-account-name\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("name"))+"\" /></p>"+
    236                 "<p><label for=\"yellow-pane-account-email\">"+this.getText("SignupEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-account-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
    237                 "<p><label for=\"yellow-pane-account-password\">"+this.getText("SignupPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"yellow-pane-account-password\" maxlength=\"64\" value=\"\" /></p>"+
    238                 "<p>"+this.getRawDataLanguages(paneId)+"</p>"+
    239                 "<p>"+this.getText("AccountInformation")+" <a href=\"#\" data-action=\"quit\">"+this.getText("AccountMore")+"</a></p>"+
    240                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("ChangeButton")+"\" /></p>"+
    241                 "</div>"+
    242                 "<div class=\"yellow-settings yellow-settings-banner\"></div>"+
    243                 "</div>"+
    244                 "</form>";
    245                 break;
    246             case "yellow-pane-configure":
    247                 elementDiv.innerHTML =
    248                 "<form method=\"post\">"+
    249                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    250                 "<div class=\"yellow-title\"><h1 id=\"yellow-pane-configure-title\">"+this.getText("ConfigureTitle")+"</h1></div>"+
    251                 "<div class=\"yellow-status\"><p id=\"yellow-pane-configure-status\" class=\""+paneStatus+"\">"+this.getText("ConfigureStatus", "", paneStatus)+"</p></div>"+
    252                 "<div class=\"yellow-settings\">"+
    253                 "<div id=\"yellow-pane-configure-settings-actions\" class=\"yellow-settings-left\"><p>"+this.getRawDataSettingsActions(paneAction)+"</p></div>"+
    254                 "<div id=\"yellow-pane-configure-settings-separator\" class=\"yellow-settings-left yellow-settings-separator\">&nbsp;</div>"+
    255                 "<div id=\"yellow-pane-configure-settings-fields\" class=\"yellow-settings-right yellow-fields\">"+
    256                 "<input type=\"hidden\" name=\"action\" value=\"configure\" />"+
    257                 "<input type=\"hidden\" name=\"yellowcsrftoken\" value=\""+yellow.toolbox.encodeHtml(this.getCookie("yellowcsrftoken"))+"\" />"+
    258                 "<p><label for=\"yellow-pane-configure-sitename\">"+this.getText("ConfigureSitename")+"</label><br /><input class=\"yellow-form-control\" name=\"sitename\" id=\"yellow-pane-configure-sitename\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("sitename"))+"\" /></p>"+
    259                 "<p><label for=\"yellow-pane-configure-author\">"+this.getText("ConfigureAuthor")+"</label><br /><input class=\"yellow-form-control\" name=\"author\" id=\"yellow-pane-configure-author\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("author"))+"\" /></p>"+
    260                 "<p><label for=\"yellow-pane-configure-email\">"+this.getText("ConfigureEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-configure-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
    261                 "<p>"+this.getText("ConfigureInformation")+"</p>"+
    262                 "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("ChangeButton")+"\" /></p>"+
    263                 "</div>"+
    264                 "<div class=\"yellow-settings yellow-settings-banner\"></div>"+
    265                 "</div>"+
    266                 "</form>";
    267                 break;
    268             case "yellow-pane-update":
    269                 elementDiv.innerHTML =
    270                 "<form method=\"post\">"+
    271                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    272                 "<div class=\"yellow-title\"><h1 id=\"yellow-pane-update-title\">"+yellow.toolbox.encodeHtml(yellow.system.coreProductRelease)+"</h1></div>"+
    273                 "<div class=\"yellow-status\"><p id=\"yellow-pane-update-status\" class=\""+paneStatus+"\">"+this.getText("UpdateStatus", "", paneStatus)+"</p></div>"+
    274                 "<div class=\"yellow-output\" id=\"yellow-pane-update-output\">"+yellow.page.rawDataOutput+"</div>"+
    275                 "<div class=\"yellow-buttons\" id=\"yellow-pane-update-buttons\">"+
    276                 "<p><a href=\"#\" id=\"yellow-pane-update-submit\" class=\"yellow-btn\" data-action=\"close\">"+this.getText("OkButton")+"</a></p>"+
    277                 "</div>"+
    278                 "</form>";
    279                 break;
    280             case "yellow-pane-create":
    281                 elementDiv.innerHTML =
    282                 "<form method=\"post\">"+
    283                 "<div id=\"yellow-pane-create-toolbar\">"+
    284                 "<div class=\"yellow-toolbar yellow-toolbar-left\"><h1 id=\"yellow-pane-create-toolbar-title\">"+this.getText("Create")+"</h1></div>"+
    285                 "<ul id=\"yellow-pane-create-toolbar-buttons\" class=\"yellow-toolbar yellow-toolbar-left\">"+this.getRawDataButtons(paneId)+"</ul>"+
    286                 "<ul id=\"yellow-pane-create-toolbar-main\" class=\"yellow-toolbar yellow-toolbar-right\">"+
    287                 "<li><a href=\"#\" id=\"yellow-pane-create-cancel\" class=\"yellow-toolbar-btn\" data-action=\"close\">"+this.getText("CancelButton")+"</a></li>"+
    288                 "<li><a href=\"#\" id=\"yellow-pane-create-submit\" class=\"yellow-toolbar-btn\" data-action=\"submit\">"+this.getText("CreateButton")+"</a></li>"+
    289                 "</ul>"+
    290                 "<ul class=\"yellow-toolbar yellow-toolbar-banner\"></ul>"+
    291                 "</div>"+
    292                 "<textarea id=\"yellow-pane-create-text\" class=\"yellow-edit-text\"></textarea>"+
    293                 "<div id=\"yellow-pane-create-preview\" class=\"yellow-edit-preview\"></div>"+
    294                 "</form>";
    295                 break;
    296             case "yellow-pane-edit":
    297                 elementDiv.innerHTML =
    298                 "<form method=\"post\">"+
    299                 "<div id=\"yellow-pane-edit-toolbar\">"+
    300                 "<div class=\"yellow-toolbar yellow-toolbar-left\"><h1 id=\"yellow-pane-edit-toolbar-title\">"+this.getText("Edit")+"</h1></div>"+
    301                 "<ul id=\"yellow-pane-edit-toolbar-buttons\" class=\"yellow-toolbar yellow-toolbar-left\">"+this.getRawDataButtons(paneId)+"</ul>"+
    302                 "<ul id=\"yellow-pane-edit-toolbar-main\" class=\"yellow-toolbar yellow-toolbar-right\">"+
    303                 "<li><a href=\"#\" id=\"yellow-pane-edit-cancel\" class=\"yellow-toolbar-btn\" data-action=\"close\">"+this.getText("CancelButton")+"</a></li>"+
    304                 "<li><a href=\"#\" id=\"yellow-pane-edit-submit\" class=\"yellow-toolbar-btn\" data-action=\"submit\">"+this.getText("EditButton")+"</a></li>"+
    305                 "</ul>"+
    306                 "<ul class=\"yellow-toolbar yellow-toolbar-banner\"></ul>"+
    307                 "</div>"+
    308                 "<textarea id=\"yellow-pane-edit-text\" class=\"yellow-edit-text\"></textarea>"+
    309                 "<div id=\"yellow-pane-edit-preview\" class=\"yellow-edit-preview\"></div>"+
    310                 "</form>";
    311                 break;
    312             case "yellow-pane-delete":
    313                 elementDiv.innerHTML =
    314                 "<form method=\"post\">"+
    315                 "<div id=\"yellow-pane-delete-toolbar\">"+
    316                 "<div class=\"yellow-toolbar yellow-toolbar-left\"><h1 id=\"yellow-pane-delete-toolbar-title\">"+this.getText("Delete")+"</h1></div>"+
    317                 "<ul id=\"yellow-pane-delete-toolbar-buttons\" class=\"yellow-toolbar yellow-toolbar-left\">"+this.getRawDataButtons(paneId)+"</ul>"+
    318                 "<ul id=\"yellow-pane-delete-toolbar-main\" class=\"yellow-toolbar yellow-toolbar-right\">"+
    319                 "<li><a href=\"#\" id=\"yellow-pane-delete-cancel\" class=\"yellow-toolbar-btn\" data-action=\"close\">"+this.getText("CancelButton")+"</a></li>"+
    320                 "<li><a href=\"#\" id=\"yellow-pane-delete-submit\" class=\"yellow-toolbar-btn\" data-action=\"submit\">"+this.getText("DeleteButton")+"</a></li>"+
    321                 "</ul>"+
    322                 "<ul class=\"yellow-toolbar yellow-toolbar-banner\"></ul>"+
    323                 "</div>"+
    324                 "<textarea id=\"yellow-pane-delete-text\" class=\"yellow-edit-text\"></textarea>"+
    325                 "<div id=\"yellow-pane-delete-preview\" class=\"yellow-edit-preview\"></div>"+
    326                 "</form>";
    327                 break;
    328             case "yellow-pane-menu":
    329                 elementDiv.innerHTML =
    330                 "<ul class=\"yellow-dropdown\">"+
    331                 "<li><span>"+yellow.toolbox.encodeHtml(yellow.user.email)+"</span></li>"+
    332                 "<li><a href=\"#\" data-action=\"settings\">"+this.getText("MenuSettings")+"</a></li>" +
    333                 "<li><a href=\"#\" data-action=\"help\">"+this.getText("MenuHelp")+"</a></li>" +
    334                 "<li><a href=\"#\" data-action=\"submit\" data-arguments=\"action:logout\">"+this.getText("MenuLogout")+"</a></li>"+
    335                 "</ul>";
    336                 break;
    337             case "yellow-pane-information":
    338                 elementDiv.innerHTML =
    339                 "<form method=\"post\">"+
    340                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    341                 "<div class=\"yellow-title\"><h1 id=\"yellow-pane-information-title\">"+this.getText(paneAction+"Title")+"</h1></div>"+
    342                 "<div class=\"yellow-status\"><p id=\"yellow-pane-information-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</p></div>"+
    343                 "<div class=\"yellow-buttons\" id=\"yellow-pane-information-buttons\">"+
    344                 "<p><a href=\"#\" class=\"yellow-btn\" data-action=\"close\">"+this.getText("OkButton")+"</a></p>"+
    345                 "</div>"+
    346                 "</form>";
    347                 break;
    348             default: elementDiv.innerHTML =
    349                 "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
    350                 "<div class=\"yellow-error\">Pane '"+paneId+"' was not found. Oh no…</div>";
    351         }
    352         elementPane.appendChild(elementDiv);
    353         yellow.toolbox.insertAfter(elementPane, document.getElementsByTagName("body")[0].firstChild);
    354         this.bindActions(elementPane);
    355     },
    356 
    357     // Update pane
    358     updatePane: function(paneId, paneAction, paneStatus, paneInit) {
    359         switch (paneId) {
    360             case "yellow-pane-login":
    361                 if (paneInit && yellow.system.editLoginRestriction) {
    362                     yellow.toolbox.setVisible(document.getElementById("yellow-pane-login-signup"), false);
    363                 }
    364                 break;
    365             case "yellow-pane-quit":
    366                 if (paneStatus=="none") {
    367                     document.getElementById("yellow-pane-quit-status").innerHTML = this.getText("QuitStatusNone");
    368                     document.getElementById("yellow-pane-quit-name").value = "";
    369                 }
    370                 break;
    371             case "yellow-pane-account":
    372                 if (paneInit && yellow.system.editSettingsActions=="none") {
    373                     document.getElementById("yellow-pane-account-title").innerHTML = this.getText("MenuSettings");
    374                 }
    375                 if (paneStatus=="none") {
    376                     document.getElementById("yellow-pane-account-status").innerHTML = this.getText("AccountStatusNone");
    377                     document.getElementById("yellow-pane-account-name").value = yellow.user.name;
    378                     document.getElementById("yellow-pane-account-email").value = yellow.user.email;
    379                     document.getElementById("yellow-pane-account-password").value = "";
    380                     if (document.getElementById("yellow-pane-account-"+yellow.user.language)) {
    381                         document.getElementById("yellow-pane-account-"+yellow.user.language).checked = true;
    382                     }
    383                 }
    384                 break;
    385             case "yellow-pane-configure":
    386                 if (paneStatus=="none") {
    387                     document.getElementById("yellow-pane-configure-status").innerHTML = this.getText("ConfigureStatusNone");
    388                     document.getElementById("yellow-pane-configure-sitename").value = yellow.system.sitename;
    389                     document.getElementById("yellow-pane-configure-author").value = yellow.system.author;
    390                     document.getElementById("yellow-pane-configure-email").value = yellow.system.email;
    391                 }
    392                 break;
    393             case "yellow-pane-update":
    394                 if (paneStatus=="none") {
    395                     document.getElementById("yellow-pane-update-status").innerHTML = this.getText("UpdateStatusCheck");
    396                     document.getElementById("yellow-pane-update-output").innerHTML = "";
    397                     setTimeout("yellow.action('submit', '', 'action:update/option:check/');", 500);
    398                 }
    399                 if (paneStatus=="updates") {
    400                     document.getElementById(paneId+"-submit").innerHTML = this.getText("UpdateButton");
    401                     document.getElementById(paneId+"-submit").setAttribute("data-action", "submit");
    402                     document.getElementById(paneId+"-submit").setAttribute("data-arguments", "action:update");
    403                 }
    404                 break;
    405             case "yellow-pane-create":
    406             case "yellow-pane-edit":
    407             case "yellow-pane-delete":
    408                 document.getElementById(paneId+"-text").focus();
    409                 if (paneInit) {
    410                     yellow.toolbox.setVisible(document.getElementById(paneId+"-text"), true);
    411                     yellow.toolbox.setVisible(document.getElementById(paneId+"-preview"), false);
    412                     document.getElementById(paneId+"-toolbar-title").innerHTML = yellow.toolbox.encodeHtml(yellow.page.title);
    413                     document.getElementById(paneId+"-text").value = paneId=="yellow-pane-create" ? yellow.page.rawDataNew : yellow.page.rawDataEdit;
    414                     var matches = document.getElementById(paneId+"-text").value.match(/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+/);
    415                     var position = document.getElementById(paneId+"-text").value.indexOf("\n", matches ? matches[0].length : 0);
    416                     document.getElementById(paneId+"-text").setSelectionRange(position, position);
    417                     if (yellow.system.editToolbarButtons!="none") {
    418                         yellow.toolbox.setVisible(document.getElementById(paneId+"-toolbar-title"), false);
    419                         this.updateToolbar(0, "yellow-toolbar-checked");
    420                     }
    421                     if (!this.isUserAccess(paneAction, yellow.page.location) || (yellow.page.rawDataReadonly && paneId!="yellow-pane-create")) {
    422                         document.getElementById(paneId+"-text").readOnly = true;
    423                         var elements = document.getElementsByClassName("yellow-toolbar-btn-icon");
    424                         for (var i=0, l=elements.length; i<l; i++) {
    425                             yellow.toolbox.addClass(elements[i], "yellow-toolbar-disabled");
    426                         }
    427                         yellow.toolbox.setVisible(document.getElementById(paneId+"-submit"), false);
    428                     }
    429                 }
    430                 if (!document.getElementById(paneId+"-text").readOnly) {
    431                     paneAction = this.paneAction = this.getPaneAction(paneId);
    432                     var className = "yellow-toolbar-btn yellow-toolbar-btn-"+paneAction;
    433                     if (document.getElementById(paneId+"-submit").className != className) {
    434                         document.getElementById(paneId+"-submit").className = className;
    435                         document.getElementById(paneId+"-submit").innerHTML = this.getText(paneAction+"Button");
    436                         document.getElementById(paneId+"-submit").setAttribute("data-arguments", "action:"+paneAction);
    437                         this.resizePane(paneId, paneAction, paneStatus);
    438                     }
    439                 }
    440                 break;
    441         }
    442         this.bindActions(document.getElementById(paneId));
    443     },
    444 
    445     // Resize pane
    446     resizePane: function(paneId, paneAction, paneStatus) {
    447         if (document.getElementById(paneId)) {
    448             var elementBar = document.getElementById("yellow-bar-content");
    449             var paneLeft = yellow.toolbox.getOuterLeft(elementBar);
    450             var paneTop = yellow.toolbox.getOuterTop(elementBar) + yellow.toolbox.getOuterHeight(elementBar) + 10;
    451             var paneWidth = yellow.toolbox.getOuterWidth(elementBar);
    452             var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - Math.min(yellow.toolbox.getOuterHeight(elementBar) + 10, (yellow.toolbox.getWindowWidth()-yellow.toolbox.getOuterWidth(elementBar))/2);
    453             switch (paneId) {
    454                 case "yellow-pane-account":
    455                 case "yellow-pane-configure":
    456                     yellow.toolbox.setOuterLeft(document.getElementById(paneId), paneLeft);
    457                     yellow.toolbox.setOuterTop(document.getElementById(paneId), paneTop);
    458                     yellow.toolbox.setOuterWidth(document.getElementById(paneId), paneWidth);
    459                     var elementWidth = yellow.toolbox.getWidth(document.getElementById(paneId));
    460                     var actionsWidth = yellow.toolbox.getOuterWidth(document.getElementById(paneId+"-settings-actions"));
    461                     var fieldsWidth = yellow.toolbox.getOuterWidth(document.getElementById(paneId+"-settings-fields"));
    462                     var separatorWidth = Math.max(10, ((elementWidth-fieldsWidth)/2)-actionsWidth);
    463                     yellow.toolbox.setOuterWidth(document.getElementById(paneId+"-settings-separator"), separatorWidth);
    464                     break;
    465                 case "yellow-pane-create":
    466                 case "yellow-pane-edit":
    467                 case "yellow-pane-delete":
    468                     yellow.toolbox.setOuterLeft(document.getElementById(paneId), paneLeft);
    469                     yellow.toolbox.setOuterTop(document.getElementById(paneId), paneTop);
    470                     yellow.toolbox.setOuterHeight(document.getElementById(paneId), paneHeight);
    471                     yellow.toolbox.setOuterWidth(document.getElementById(paneId), paneWidth);
    472                     var elementWidth = yellow.toolbox.getWidth(document.getElementById(paneId));
    473                     yellow.toolbox.setOuterWidth(document.getElementById(paneId+"-text"), elementWidth);
    474                     yellow.toolbox.setOuterWidth(document.getElementById(paneId+"-preview"), elementWidth);
    475                     var buttonsWidth = 0;
    476                     var buttonsWidthMax = yellow.toolbox.getOuterWidth(document.getElementById(paneId+"-toolbar")) -
    477                         yellow.toolbox.getOuterWidth(document.getElementById(paneId+"-toolbar-main")) - 1;
    478                     var element = document.getElementById(paneId+"-toolbar-buttons").firstChild;
    479                     for (; element; element=element.nextSibling) {
    480                         element.removeAttribute("style");
    481                         buttonsWidth += yellow.toolbox.getOuterWidth(element);
    482                         if (buttonsWidth>buttonsWidthMax) yellow.toolbox.setVisible(element, false);
    483                     }
    484                     yellow.toolbox.setOuterWidth(document.getElementById(paneId+"-toolbar-title"), buttonsWidthMax);
    485                     var height1 = yellow.toolbox.getHeight(document.getElementById(paneId));
    486                     var height2 = yellow.toolbox.getOuterHeight(document.getElementById(paneId+"-toolbar"));
    487                     yellow.toolbox.setOuterHeight(document.getElementById(paneId+"-text"), height1 - height2);
    488                     yellow.toolbox.setOuterHeight(document.getElementById(paneId+"-preview"), height1 - height2);
    489                     var elementLink = document.getElementById(paneId+"-bar");
    490                     var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
    491                     position -= yellow.toolbox.getOuterLeft(document.getElementById(paneId)) + 1;
    492                     yellow.toolbox.setOuterLeft(document.getElementById(paneId+"-arrow"), position);
    493                     break;
    494                 case "yellow-pane-menu":
    495                     yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-menu"), paneLeft + paneWidth - yellow.toolbox.getOuterWidth(document.getElementById("yellow-pane-menu")));
    496                     yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-menu"), paneTop);
    497                     var elementLink = document.getElementById("yellow-pane-menu-bar");
    498                     var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
    499                     position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-menu"));
    500                     yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-menu-arrow"), position);
    501                     break;
    502                 default:
    503                     yellow.toolbox.setOuterLeft(document.getElementById(paneId), paneLeft);
    504                     yellow.toolbox.setOuterTop(document.getElementById(paneId), paneTop);
    505                     yellow.toolbox.setOuterWidth(document.getElementById(paneId), paneWidth);
    506                     break;
    507             }
    508         }
    509     },
    510     
    511     // Show or hide pane
    512     showPane: function(paneId, paneAction, paneStatus, paneModal) {
    513         if (this.paneId!=paneId || this.paneAction!=paneAction) {
    514             this.hidePane(this.paneId);
    515             var paneInit = !document.getElementById(paneId);
    516             if (!document.getElementById(paneId)) this.createPane(paneId, paneAction, paneStatus);
    517             var element = document.getElementById(paneId);
    518             if (!yellow.toolbox.isVisible(element)) {
    519                 if (yellow.system.coreDebugMode) console.log("yellow.edit.showPane id:"+paneId);
    520                 yellow.toolbox.setVisible(element, true);
    521                 if (paneModal) {
    522                     yellow.toolbox.addClass(document.body, "yellow-body-modal-open");
    523                     yellow.toolbox.addValue("meta[name=viewport]", "content", ", maximum-scale=1, user-scalable=0");
    524                 }
    525                 this.paneId = paneId;
    526                 this.paneAction = paneAction;
    527                 this.paneStatus = paneStatus;
    528                 this.updatePane(paneId, paneAction, paneStatus, paneInit);
    529                 this.resizePane(paneId, paneAction, paneStatus);
    530                 this.updateBar(paneId, "yellow-bar-selected");
    531             }
    532         } else {
    533             this.hidePane(this.paneId, true);
    534         }
    535     },
    536 
    537     // Hide pane
    538     hidePane: function(paneId, fadeout) {
    539         var element = document.getElementById(paneId);
    540         if (yellow.toolbox.isVisible(element)) {
    541             if (yellow.system.coreDebugMode) console.log("yellow.edit.hidePane id:"+paneId);
    542             yellow.toolbox.removeClass(document.body, "yellow-body-modal-open");
    543             yellow.toolbox.removeValue("meta[name=viewport]", "content", ", maximum-scale=1, user-scalable=0");
    544             yellow.toolbox.setVisible(element, false, fadeout);
    545             this.paneId = 0;
    546             this.paneAction = 0;
    547             this.paneStatus = 0;
    548             this.updateBar(0, "yellow-bar-selected");
    549         }
    550         this.hidePopup(this.popupId);
    551     },
    552     
    553     // Process action
    554     processAction: function(action, status, arguments) {
    555         action = action ? action : "none";
    556         status = status ? status : "none";
    557         arguments = arguments ? arguments : "none";
    558         if (action!="none") {
    559             if (yellow.system.coreDebugMode) console.log("yellow.edit.processAction action:"+action+" status:"+status);
    560             var paneId = (status!="next" && status!="done") ? "yellow-pane-"+action : "yellow-pane-information";
    561             switch(action) {
    562                 case "login":       this.showPane(paneId, action, status); break;
    563                 case "signup":      this.showPane(paneId, action, status); break;
    564                 case "confirm":     this.showPane(paneId, action, status); break;
    565                 case "approve":     this.showPane(paneId, action, status); break;
    566                 case "forgot":      this.showPane(paneId, action, status); break;
    567                 case "recover":     this.showPane(paneId, action, status); break;
    568                 case "reactivate":  this.showPane(paneId, action, status); break;
    569                 case "verify":      this.showPane(paneId, action, status); break;
    570                 case "change":      this.showPane(paneId, action, status); break;
    571                 case "quit":        this.showPane(paneId, action, status); break;
    572                 case "remove":      this.showPane(paneId, action, status); break;
    573                 case "account":     this.showPane(paneId, action, status); break;
    574                 case "configure":   this.showPane(paneId, action, status); break;
    575                 case "update":      this.showPane(paneId, action, status); break;
    576                 case "create":      this.showPane(paneId, action, status, true); break;
    577                 case "edit":        this.showPane(paneId, action, status, true); break;
    578                 case "delete":      this.showPane(paneId, action, status, true); break;
    579                 case "menu":        this.showPane(paneId, action, status); break;
    580                 case "toolbar":     this.processToolbar(status, arguments); break;
    581                 case "settings":    this.processSettings(arguments); break;
    582                 case "submit":      this.processSubmit(arguments); break;
    583                 case "restore":     this.processSubmit("action:"+action); break;
    584                 case "help":        this.processHelp(); break;
    585                 case "close":       this.processClose(); break;
    586             }
    587         }
    588     },
    589     
    590     // Process toolbar
    591     processToolbar: function(status, arguments) {
    592         if (yellow.system.coreDebugMode) console.log("yellow.edit.processToolbar status:"+status);
    593         var elementText = document.getElementById(this.paneId+"-text");
    594         var elementPreview = document.getElementById(this.paneId+"-preview");
    595         if (!yellow.toolbox.isVisible(elementPreview) && !elementText.readOnly) {
    596             switch (status) {
    597                 case "h1":              yellow.editor.setMarkdown(elementText, "# ", "insert-multiline-block", "", true); break;
    598                 case "h2":              yellow.editor.setMarkdown(elementText, "## ", "insert-multiline-block", "", true); break;
    599                 case "h3":              yellow.editor.setMarkdown(elementText, "### ", "insert-multiline-block", "", true); break;
    600                 case "paragraph":       yellow.editor.setMarkdown(elementText, "", "remove-multiline-block");
    601                                         yellow.editor.setMarkdown(elementText, "", "remove-fenced-block"); break;
    602                 case "important":       yellow.editor.setMarkdown(elementText, "! ", "insert-multiline-block", "important", true); break;
    603                 case "quote":           yellow.editor.setMarkdown(elementText, "> ", "insert-multiline-block", "", true); break;
    604                 case "pre":             yellow.editor.setMarkdown(elementText, "```\n", "insert-fenced-block", "", true); break;
    605                 case "bold":            yellow.editor.setMarkdown(elementText, "**", "insert-inline", "", true); break;
    606                 case "italic":          yellow.editor.setMarkdown(elementText, "*", "insert-inline", "", true); break;
    607                 case "strikethrough":   yellow.editor.setMarkdown(elementText, "~~", "insert-inline", "", true); break;
    608                 case "code":            yellow.editor.setMarkdown(elementText, "`", "insert-autodetect", "", true); break;
    609                 case "ul":              yellow.editor.setMarkdown(elementText, "* ", "insert-multiline-block", "", true); break;
    610                 case "ol":              yellow.editor.setMarkdown(elementText, "1. ", "insert-multiline-block", "", true); break;
    611                 case "tl":              yellow.editor.setMarkdown(elementText, "- [ ] ", "insert-multiline-block", "", true); break;
    612                 case "link":            yellow.editor.setMarkdown(elementText, "[link](url)", "insert", "", false, yellow.editor.getMarkdownLink); break;
    613                 case "text":            yellow.editor.setMarkdown(elementText, arguments, "insert"); break;
    614                 case "status":          yellow.editor.setMetaData(elementText, "status", true); break;
    615                 case "file":            this.showFileDialog(); break;
    616                 case "undo":            yellow.editor.undo(); break;
    617                 case "redo":            yellow.editor.redo(); break;
    618             }
    619             if (this.isExpandable(status)) {
    620                 this.showPopup("yellow-popup-"+status, status);
    621             } else {
    622                 this.hidePopup(this.popupId);
    623             }
    624         }
    625         if (!elementText.readOnly) {
    626             if (status=="preview") this.showPreview(elementText, elementPreview);
    627             if (status=="save" && this.paneAction!="delete") this.processSubmit("action:"+this.paneAction);
    628             if (status=="help") window.open(this.getText("YellowHelpUrl"), "_blank");
    629         }
    630     },
    631     
    632     // Update toolbar
    633     updateToolbar: function(status, name) {
    634         if (status) {
    635             var element = document.getElementById(this.paneId+"-toolbar-"+status);
    636             if (element) {
    637                 if (name.indexOf("selected")!=-1) element.setAttribute("aria-expanded", "true");
    638                 yellow.toolbox.addClass(element, name);
    639             }
    640         } else {
    641             var elements = document.getElementsByClassName(name);
    642             for (var i=0, l=elements.length; i<l; i++) {
    643                 if (name.indexOf("selected")!=-1) elements[i].setAttribute("aria-expanded", "false");
    644                 yellow.toolbox.removeClass(elements[i], name);
    645             }
    646         }
    647     },
    648     
    649     // Process shortcut
    650     processShortcut: function(e) {
    651         var shortcut = yellow.toolbox.getEventShortcut(e);
    652         if (shortcut) {
    653             var tokens = yellow.system.editKeyboardShortcuts.split(/\s*,\s*/);
    654             for (var i=0; i<tokens.length; i++) {
    655                 var pair = tokens[i].split(" ");
    656                 if (shortcut==pair[0] || shortcut.replace("meta+", "ctrl+")==pair[0]) {
    657                     if (yellow.system.coreDebugMode) console.log("yellow.edit.processShortcut shortcut:"+shortcut);
    658                     e.stopPropagation();
    659                     e.preventDefault();
    660                     this.processToolbar(pair[1]);
    661                 }
    662             }
    663         }
    664     },
    665     
    666     // Process settings
    667     processSettings: function(arguments) {
    668         var action = arguments!="none" ? arguments : "account";
    669         if (action!=this.paneAction && action!="settings") this.processAction(action);
    670     },
    671     
    672     // Process submit
    673     processSubmit: function(arguments) {
    674         var settings = { "action":"none", "yellowcsrftoken":this.getCookie("yellowcsrftoken") };
    675         var tokens = arguments.split("/");
    676         for (var i=0; i<tokens.length; i++) {
    677             var pair = tokens[i].split(/[:=]/);
    678             if (!pair[0] || !pair[1]) continue;
    679             settings[pair[0]] = pair[1];
    680         }
    681         if (settings["action"]=="create" || settings["action"]=="edit" || settings["action"]=="delete") {
    682             settings.rawdatasource = yellow.page.rawDataSource;
    683             settings.rawdataedit = document.getElementById(this.paneId+"-text").value;
    684             settings.rawdataendofline = yellow.page.rawDataEndOfLine;
    685         }
    686         if (settings["action"]!="none") yellow.toolbox.submitForm(settings);
    687     },
    688     
    689     // Process help
    690     processHelp: function() {
    691         this.hidePane(this.paneId);
    692         window.open(this.getText("YellowHelpUrl"), "_self");
    693     },
    694     
    695     // Process close
    696     processClose: function() {
    697         this.hidePane(this.paneId);
    698         if (yellow.page.action=="login") {
    699             var url = yellow.system.coreServerScheme+"://"+
    700                 yellow.system.coreServerAddress+
    701                 yellow.system.coreServerBase+
    702                 yellow.page.location;
    703             window.open(url, "_self");
    704         }
    705     },
    706     
    707     // Create popup
    708     createPopup: function(popupId) {
    709         if (yellow.system.coreDebugMode) console.log("yellow.edit.createPopup id:"+popupId);
    710         var elementPopup = document.createElement("div");
    711         elementPopup.className = "yellow-popup";
    712         elementPopup.setAttribute("id", popupId);
    713         elementPopup.style.display = "none";
    714         var elementDiv = document.createElement("div");
    715         elementDiv.setAttribute("id", popupId+"-content");
    716         switch (popupId) {
    717             case "yellow-popup-format":
    718                 elementDiv.innerHTML =
    719                 "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+
    720                 "<li><a href=\"#\" id=\"yellow-popup-format-h1\" data-action=\"toolbar\" data-status=\"h1\">"+this.getText("ToolbarH1")+"</a></li>"+
    721                 "<li><a href=\"#\" id=\"yellow-popup-format-h2\" data-action=\"toolbar\" data-status=\"h2\">"+this.getText("ToolbarH2")+"</a></li>"+
    722                 "<li><a href=\"#\" id=\"yellow-popup-format-h3\" data-action=\"toolbar\" data-status=\"h3\">"+this.getText("ToolbarH3")+"</a></li>"+
    723                 "<li><a href=\"#\" id=\"yellow-popup-format-paragraph\" data-action=\"toolbar\" data-status=\"paragraph\">"+this.getText("ToolbarParagraph")+"</a></li>"+
    724                 "<li><a href=\"#\" id=\"yellow-popup-format-important\" data-action=\"toolbar\" data-status=\"important\">"+this.getText("ToolbarImportant")+"</a></li>"+
    725                 "<li><a href=\"#\" id=\"yellow-popup-format-pre\" data-action=\"toolbar\" data-status=\"pre\">"+this.getText("ToolbarPre")+"</a></li>"+
    726                 "<li><a href=\"#\" id=\"yellow-popup-format-quote\" data-action=\"toolbar\" data-status=\"quote\">"+this.getText("ToolbarQuote")+"</a></li>"+
    727                 "</ul>";
    728                 break;
    729             case "yellow-popup-heading":
    730                 elementDiv.innerHTML =
    731                 "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+
    732                 "<li><a href=\"#\" id=\"yellow-popup-heading-h1\" data-action=\"toolbar\" data-status=\"h1\">"+this.getText("ToolbarH1")+"</a></li>"+
    733                 "<li><a href=\"#\" id=\"yellow-popup-heading-h2\" data-action=\"toolbar\" data-status=\"h2\">"+this.getText("ToolbarH2")+"</a></li>"+
    734                 "<li><a href=\"#\" id=\"yellow-popup-heading-h3\" data-action=\"toolbar\" data-status=\"h3\">"+this.getText("ToolbarH3")+"</a></li>"+
    735                 "</ul>";
    736                 break;
    737             case "yellow-popup-list":
    738                 elementDiv.innerHTML =
    739                 "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+
    740                 "<li><a href=\"#\" id=\"yellow-popup-list-ul\" data-action=\"toolbar\" data-status=\"ul\">"+this.getText("ToolbarUl")+"</a></li>"+
    741                 "<li><a href=\"#\" id=\"yellow-popup-list-ol\" data-action=\"toolbar\" data-status=\"ol\">"+this.getText("ToolbarOl")+"</a></li>"+
    742                 "<li><a href=\"#\" id=\"yellow-popup-list-tl\" data-action=\"toolbar\" data-status=\"tl\">"+this.getText("ToolbarTl")+"</a></li>"+
    743                 "</ul>";
    744                 break;
    745             case "yellow-popup-emoji":
    746                 var rawDataEmojis = "";
    747                 if (yellow.system.emojiToolbarButtons && yellow.system.emojiToolbarButtons!="none") {
    748                     var tokens = yellow.system.emojiToolbarButtons.split(" ");
    749                     for (var i=0; i<tokens.length; i++) {
    750                         var token = tokens[i].replace(/[\:]/g,"");
    751                         var className = token.replace("+1", "plus1").replace("-1", "minus1").replace(/_/g, "-");
    752                         rawDataEmojis += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"emoji emoji-"+yellow.toolbox.encodeHtml(className)+"\"></i></a></li>";
    753                     }
    754                 }
    755                 elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataEmojis+"</ul>";
    756                 break;
    757             case "yellow-popup-icon":
    758                 var rawDataIcons = "";
    759                 if (yellow.system.iconToolbarButtons && yellow.system.iconToolbarButtons!="none") {
    760                     var tokens = yellow.system.iconToolbarButtons.split(" ");
    761                     for (var i=0; i<tokens.length; i++) {
    762                         var token = tokens[i].replace(/[\:]/g,"");
    763                         rawDataIcons += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"icon "+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>";
    764                     }
    765                 }
    766                 elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataIcons+"</ul>";
    767                 break;
    768         }
    769         elementPopup.appendChild(elementDiv);
    770         yellow.toolbox.insertAfter(elementPopup, document.getElementsByTagName("body")[0].firstChild);
    771         this.bindActions(elementPopup);
    772     },
    773     
    774     // Show or hide popup
    775     showPopup: function(popupId, status) {
    776         if (this.popupId!=popupId) {
    777             this.hidePopup(this.popupId);
    778             if (!document.getElementById(popupId)) this.createPopup(popupId);
    779             var element = document.getElementById(popupId);
    780             if (yellow.system.coreDebugMode) console.log("yellow.edit.showPopup id:"+popupId);
    781             yellow.toolbox.setVisible(element, true);
    782             this.popupId = popupId;
    783             this.updateToolbar(status, "yellow-toolbar-selected");
    784             var elementParent = document.getElementById(this.paneId+"-toolbar-"+status);
    785             var popupLeft = yellow.toolbox.getOuterLeft(elementParent);
    786             var popupTop = yellow.toolbox.getOuterTop(elementParent) + yellow.toolbox.getOuterHeight(elementParent) - 1;
    787             yellow.toolbox.setOuterLeft(document.getElementById(popupId), popupLeft);
    788             yellow.toolbox.setOuterTop(document.getElementById(popupId), popupTop);
    789         } else {
    790             this.hidePopup(this.popupId, true);
    791         }
    792     },
    793     
    794     // Hide popup
    795     hidePopup: function(popupId, fadeout) {
    796         var element = document.getElementById(popupId);
    797         if (yellow.toolbox.isVisible(element)) {
    798             if (yellow.system.coreDebugMode) console.log("yellow.edit.hidePopup id:"+popupId);
    799             yellow.toolbox.setVisible(element, false, fadeout);
    800             this.popupId = 0;
    801             this.updateToolbar(0, "yellow-toolbar-selected");
    802         }
    803     },
    804     
    805     // Show or hide preview
    806     showPreview: function(elementText, elementPreview) {
    807         if (!yellow.toolbox.isVisible(elementPreview)) {
    808             var thisObject = this;
    809             var formData = new FormData();
    810             formData.append("action", "preview");
    811             formData.append("yellowcsrftoken", this.getCookie("yellowcsrftoken"));
    812             formData.append("rawdataedit", elementText.value);
    813             formData.append("rawdataendofline", yellow.page.rawDataEndOfLine);
    814             var request = new XMLHttpRequest();
    815             request.open("POST", window.location.pathname, true);
    816             request.onload = function() { if (this.status==200) thisObject.showPreviewDone.call(thisObject, elementText, elementPreview, this.responseText); };
    817             request.send(formData);
    818         } else {
    819             this.showPreviewDone(elementText, elementPreview, "");
    820         }
    821     },
    822     
    823     // Preview done
    824     showPreviewDone: function(elementText, elementPreview, responseText) {
    825         var showPreview = responseText.length!=0;
    826         yellow.toolbox.setVisible(elementText, !showPreview);
    827         yellow.toolbox.setVisible(elementPreview, showPreview);
    828         if (showPreview) {
    829             this.updateToolbar("preview", "yellow-toolbar-checked");
    830             elementPreview.innerHTML = responseText;
    831             dispatchEvent(new Event("DOMContentLoaded"));
    832         } else {
    833             this.updateToolbar(0, "yellow-toolbar-checked");
    834             elementText.focus();
    835         }
    836     },
    837     
    838     // Show file dialog and trigger upload
    839     showFileDialog: function() {
    840         var element = document.createElement("input");
    841         element.setAttribute("id", "yellow-file-dialog");
    842         element.setAttribute("type", "file");
    843         element.setAttribute("accept", yellow.system.editUploadExtensions);
    844         element.setAttribute("multiple", "multiple");
    845         yellow.toolbox.addEvent(element, "change", yellow.onDrop);
    846         element.click();
    847     },
    848     
    849     // Upload file
    850     uploadFile: function(elementText, file) {
    851         if (this.isUserAccess("upload", yellow.page.location)) {
    852             var extension = (file.name.lastIndexOf(".")!=-1 ? file.name.substring(file.name.lastIndexOf("."), file.name.length) : "").toLowerCase();
    853             var extensions = yellow.system.editUploadExtensions.split(/\s*,\s*/);
    854             if (file.size<=yellow.system.coreFileSizeMax && extensions.indexOf(extension)!=-1) {
    855                 var text = "["+this.getText("UploadProgress")+"]\u200b";
    856                 yellow.editor.setMarkdown(elementText, text, "insert");
    857                 var thisObject = this;
    858                 var formData = new FormData();
    859                 formData.append("action", "upload");
    860                 formData.append("yellowcsrftoken", this.getCookie("yellowcsrftoken"));
    861                 formData.append("file", file);
    862                 var request = new XMLHttpRequest();
    863                 request.open("POST", window.location.pathname, true);
    864                 request.onload = function() { if (this.status==200) { thisObject.uploadFileDone.call(thisObject, elementText, this.responseText); } else { thisObject.uploadFileError.call(thisObject, elementText, this.responseText); } };
    865                 request.send(formData);
    866             } else {
    867                 var textError = extensions.indexOf(extension)!=-1 ? "file too big!" : "file format not supported!";
    868                 var textNew = "[Can't upload file '"+file.name+"', "+textError+"]";
    869                 yellow.editor.setMarkdown(elementText, textNew, "insert");
    870             }
    871         } else {
    872             var textNew = "[Can't upload file '"+file.name+"', access is restricted!]";
    873             yellow.editor.setMarkdown(elementText, textNew, "insert");
    874         }
    875     },
    876     
    877     // Upload done
    878     uploadFileDone: function(elementText, responseText) {
    879         var result = JSON.parse(responseText);
    880         if (result) {
    881             var textOld = "["+this.getText("UploadProgress")+"]\u200b";
    882             var textNew;
    883             if (result.location.substring(0, yellow.system.coreImageLocation.length)==yellow.system.coreImageLocation) {
    884                 textNew = "[image "+result.location.substring(yellow.system.coreImageLocation.length)+"]";
    885             } else {
    886                 textNew = "[link]("+result.location+")";
    887             }
    888             yellow.editor.replace(elementText, textOld, textNew);
    889         }
    890     },
    891     
    892     // Upload error
    893     uploadFileError: function(elementText, responseText) {
    894         var result = JSON.parse(responseText);
    895         if (result) {
    896             var textOld = "["+this.getText("UploadProgress")+"]\u200b";
    897             var textNew = "["+result.error+"]";
    898             yellow.editor.replace(elementText, textOld, textNew);
    899         }
    900     },
    901 
    902     // Bind actions to links
    903     bindActions: function(element) {
    904         var elements = element.getElementsByTagName("a");
    905         for (var i=0, l=elements.length; i<l; i++) {
    906             if (elements[i].getAttribute("href") && elements[i].getAttribute("href").indexOf("#data-action-")!=-1) {
    907                 var position = elements[i].getAttribute("href").indexOf("#data-action-");
    908                 var action = elements[i].getAttribute("href").substring(position+13);
    909                 var href = elements[i].getAttribute("href").substring(0, position);
    910                 if (href=="" || href==yellow.page.base+yellow.page.location) elements[i].setAttribute("data-action", action);
    911             }
    912             if (elements[i].getAttribute("data-action")) elements[i].onclick = yellow.onClickAction;
    913             if (elements[i].getAttribute("data-action")=="toolbar") elements[i].onmousedown = function(e) { e.preventDefault(); };
    914         }
    915     },
    916     
    917     // Return pane action
    918     getPaneAction: function(paneId) {
    919         var panePrefix = "yellow-pane-";
    920         var paneAction = paneId.substring(panePrefix.length);
    921         if (paneAction=="edit") {
    922             if (document.getElementById("yellow-pane-edit-text").value.length==0) paneAction = "delete";
    923             if (yellow.page.statusCode==434 || yellow.page.statusCode==435) paneAction = "create";
    924         }
    925         return paneAction;
    926     },
    927     
    928     // Return raw data for pane action
    929     getRawDataPaneAction: function(paneAction, text, important) {
    930         var rawDataAction = "";
    931         if (this.isUserAccess(paneAction) || important) {
    932             if (!text) text = this.getText(paneAction);
    933             rawDataAction = "<a href=\"#\" id=\"yellow-pane-"+paneAction+"-bar\" data-action=\""+paneAction+"\" aria-expanded=\"false\">"+yellow.toolbox.encodeHtml(text)+"</a>";
    934         }
    935         return rawDataAction;
    936     },
    937     
    938     // Return raw data for settings actions
    939     getRawDataSettingsActions: function(paneAction) {
    940         var rawDataActions = "";
    941         if (yellow.system.editSettingsActions && yellow.system.editSettingsActions!="none") {
    942             var tokens = yellow.system.editSettingsActions.split(/\s*,\s*/);
    943             for (var i=0; i<tokens.length; i++) {
    944                 var token = tokens[i];
    945                 rawDataActions += "<a href=\"#\""+(token==paneAction ? "class=\"active\"": "")+" data-action=\"settings\" data-arguments=\""+yellow.toolbox.encodeHtml(token)+"\">"+this.getText(token+"Title")+"</a><br />";
    946             }
    947         }
    948         return rawDataActions;
    949     },
    950     
    951     // Return raw data for languages
    952     getRawDataLanguages: function(paneId) {
    953         var rawDataLanguages = "";
    954         if (yellow.system.coreLanguages && Object.keys(yellow.system.coreLanguages).length>1) {
    955             for (var language in yellow.system.coreLanguages) {
    956                 var checked = language==this.getRequest("language") ? " checked=\"checked\"" : "";
    957                 rawDataLanguages += "<label for=\""+paneId+"-"+language+"\"><input type=\"radio\" name=\"language\" id=\""+paneId+"-"+language+"\" value=\""+language+"\""+checked+"> "+yellow.toolbox.encodeHtml(yellow.system.coreLanguages[language])+"</label><br />";
    958             }
    959         }
    960         return rawDataLanguages;
    961     },
    962     
    963     // Return raw data for buttons
    964     getRawDataButtons: function(paneId) {
    965         var rawDataButtons = "";
    966         if (yellow.system.editToolbarButtons && yellow.system.editToolbarButtons!="none") {
    967             var tokens = yellow.system.editToolbarButtons.split(/\s*,\s*/);
    968             for (var i=0; i<tokens.length; i++) {
    969                 var token = tokens[i];
    970                 if (token!="separator") {
    971                     var shortcut = this.getShortcut(token);
    972                     var rawDataShortcut = shortcut ? "&nbsp;&nbsp;"+yellow.toolbox.encodeHtml(shortcut) : "";
    973                     var rawDataExpandable = this.isExpandable(token) ? " aria-expanded=\"false\"" : "";
    974                     rawDataButtons += "<li><a href=\"#\" id=\""+paneId+"-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)+rawDataShortcut+"\""+rawDataExpandable+"><i class=\"yellow-icon yellow-icon-"+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>";
    975                 } else {
    976                     rawDataButtons += "<li><a href=\"#\" class=\"yellow-toolbar-btn-separator\"></a></li>";
    977                 }
    978             }
    979         }
    980         return rawDataButtons;
    981     },
    982     
    983     // Return request data
    984     getRequest: function(key, prefix) {
    985         if (!prefix) prefix = "request";
    986         key = prefix + yellow.toolbox.toUpperFirst(key);
    987         return (key in yellow.page) ? yellow.page[key] : "";
    988     },
    989     
    990     // Return shortcut setting
    991     getShortcut: function(key) {
    992         var shortcut = "";
    993         var tokens = yellow.system.editKeyboardShortcuts.split(/\s*,\s*/);
    994         for (var i=0; i<tokens.length; i++) {
    995             var pair = tokens[i].split(" ");
    996             if (key==pair[1]) {
    997                 shortcut = pair[0];
    998                 break;
    999             }
   1000         }
   1001         var labels = yellow.language.editKeyboardLabels.split(/\s*,\s*/);
   1002         if (navigator.platform.indexOf("Mac")==-1) {
   1003             shortcut = shortcut.toUpperCase().replace("CTRL+", labels[0]).replace("ALT+", labels[1]).replace("SHIFT+", labels[2]);
   1004         } else {
   1005             shortcut = shortcut.toUpperCase().replace("CTRL+ALT+", "ALT+CTRL+").replace("CTRL+SHIFT+", "SHIFT+CTRL+");
   1006             shortcut = shortcut.replace("CTRL+", labels[3]).replace("ALT+", labels[4]).replace("SHIFT+", labels[5]);
   1007         }
   1008         return shortcut;
   1009     },
   1010 
   1011     // Return text setting
   1012     getText: function(key, prefix, postfix) {
   1013         if (!prefix) prefix = "edit";
   1014         if (!postfix) postfix = "";
   1015         key = prefix + yellow.toolbox.toUpperFirst(key) + yellow.toolbox.toUpperFirst(postfix);
   1016         return (key in yellow.language) ? yellow.language[key] : "["+key+"]";
   1017     },
   1018 
   1019     // Return browser cookie
   1020     getCookie: function(key) {
   1021         return yellow.toolbox.getCookie(key);
   1022     },
   1023     
   1024     // Check if user with access
   1025     isUserAccess: function(action, location) {
   1026         var tokens = yellow.user.access.split(/\s*,\s*/);
   1027         return tokens.indexOf(action)!=-1 && (!location || location.substring(0, yellow.user.home.length)==yellow.user.home);
   1028     },
   1029 
   1030     // Check if element is expandable
   1031     isExpandable: function(name) {
   1032         return (name=="format" || name=="heading" || name=="list" || name=="emoji" || name=="icon");
   1033     },
   1034     
   1035     // Check if extension exists
   1036     isExtension: function(name) {
   1037         return name in yellow.system.coreExtensions;
   1038     }
   1039 };
   1040 
   1041 yellow.editor = {
   1042 
   1043     // Set Markdown formatting
   1044     setMarkdown: function(element, prefix, type, name, toggle, callback) {
   1045         var information = this.getMarkdownInformation(element, prefix, type, name);
   1046         var selectionStart = (information.type.indexOf("block")!=-1) ? information.top : information.start;
   1047         var selectionEnd = (information.type.indexOf("block")!=-1) ? information.bottom : information.end;
   1048         if (information.found && toggle) information.type = information.type.replace("insert", "remove");
   1049         if (information.type=="remove-fenced-block" || information.type=="remove-inline") {
   1050             selectionStart -= information.prefix.length; selectionEnd += information.prefix.length;
   1051         }
   1052         var text = information.text;
   1053         var textSelectionBefore = text.substring(0, selectionStart);
   1054         var textSelection = text.substring(selectionStart, selectionEnd);
   1055         var textSelectionAfter = text.substring(selectionEnd, text.length);
   1056         var textSelectionNew, selectionStartNew, selectionEndNew;
   1057         switch (information.type) {
   1058             case "insert-multiline-block":
   1059             case "remove-multiline-block":
   1060                 textSelectionNew = this.getMarkdownMultilineBlock(textSelection, information);
   1061                 selectionStartNew = information.top;
   1062                 selectionEndNew = information.bottom + this.getMarkdownDifference(textSelection, textSelectionNew);
   1063                 break;
   1064             case "insert-fenced-block":
   1065                 textSelectionNew = this.getMarkdownFencedBlock(textSelection, information);
   1066                 selectionStartNew = information.top + information.prefix.length;
   1067                 selectionEndNew = information.bottom + this.getMarkdownDifference(textSelection, textSelectionNew) - information.prefix.length;
   1068                 break;
   1069             case "remove-fenced-block":
   1070                 textSelectionNew = this.getMarkdownFencedBlock(textSelection, information);
   1071                 selectionStartNew = information.top - information.prefix.length;
   1072                 selectionEndNew = information.bottom + this.getMarkdownDifference(textSelection, textSelectionNew) + information.prefix.length;
   1073                 break;
   1074             case "insert-inline":
   1075                 textSelectionNew = information.prefix + textSelection + information.prefix;
   1076                 selectionStartNew = information.start + information.prefix.length;
   1077                 selectionEndNew = information.end + information.prefix.length;
   1078                 break;
   1079             case "remove-inline":
   1080                 textSelectionNew = text.substring(information.start, information.end);
   1081                 selectionStartNew = information.start - information.prefix.length;
   1082                 selectionEndNew = information.end - information.prefix.length;
   1083                 break;
   1084             case "insert":
   1085                 textSelectionNew = callback ? callback(textSelection, information) : information.prefix;
   1086                 selectionStartNew = information.start + textSelectionNew.length;
   1087                 selectionEndNew = selectionStartNew;
   1088         }
   1089         if (textSelection!=textSelectionNew || selectionStart!=selectionStartNew || selectionEnd!=selectionEndNew) {
   1090             element.focus();
   1091             element.setSelectionRange(selectionStart, selectionEnd);
   1092             document.execCommand("insertText", false, textSelectionNew);
   1093             element.value = textSelectionBefore + textSelectionNew + textSelectionAfter;
   1094             element.setSelectionRange(selectionStartNew, selectionEndNew);
   1095         }
   1096         if (yellow.system.coreDebugMode) console.log("yellow.editor.setMarkdown type:"+information.type);
   1097     },
   1098     
   1099     // Return Markdown formatting information
   1100     getMarkdownInformation: function(element, prefix, type, name) {
   1101         var text = element.value;
   1102         var start = element.selectionStart;
   1103         var end = element.selectionEnd;
   1104         var top = start, bottom = end;
   1105         while (text.charAt(top-1)!="\n" && top>0) top--;
   1106         if (bottom==top && bottom<text.length) bottom++;
   1107         while (text.charAt(bottom-1)!="\n" && bottom<text.length) bottom++;
   1108         if (type=="insert-autodetect") {
   1109             if (text.substring(start, end).indexOf("\n")!=-1) {
   1110                 type = "insert-fenced-block"; prefix = "```\n";
   1111             } else {
   1112                 type = "insert-inline"; prefix = "`";
   1113             }
   1114         }
   1115         var attributes = name ? prefix+" {."+name+"}\n" : "";
   1116         var found = false;
   1117         if (type.indexOf("multiline-block")!=-1) {
   1118             if (text.substring(top, top+prefix.length)==prefix) found = true;
   1119         } else if (type.indexOf("fenced-block")!=-1) {
   1120             if (text.substring(top-prefix.length, top)==prefix && text.substring(bottom, bottom+prefix.length)==prefix) {
   1121                 found = true;
   1122             }
   1123         } else {
   1124             if (text.substring(start-prefix.length, start)==prefix && text.substring(end, end+prefix.length)==prefix) {
   1125                 if (prefix=="*") {
   1126                     var lettersBefore = 0, lettersAfter = 0;
   1127                     for (var index=start-1; text.charAt(index)=="*"; index--) lettersBefore++;
   1128                     for (var index=end; text.charAt(index)=="*"; index++) lettersAfter++;
   1129                     found = lettersBefore!=2 && lettersAfter!=2;
   1130                 } else {
   1131                     found = true;
   1132                 }
   1133             }
   1134         }
   1135         return { "text":text, "prefix":prefix, "type":type, "attributes":attributes, "start":start, "end":end, "top":top, "bottom":bottom, "found":found };
   1136     },
   1137     
   1138     // Return Markdown length difference
   1139     getMarkdownDifference: function(textSelection, textSelectionNew) {
   1140         var position = textSelection.indexOf("\n");
   1141         var positionNew = textSelectionNew.indexOf("\n");
   1142         var textSelectionLength = position!=-1 ? textSelection.length : textSelection.length+1;
   1143         var textSelectionLengthNew = positionNew!=-1 ? textSelectionNew.length : textSelectionNew.length+1;
   1144         return textSelectionLengthNew - textSelectionLength;
   1145     },
   1146     
   1147     // Return Markdown for multiline block
   1148     getMarkdownMultilineBlock: function(textSelection, information) {
   1149         var textSelectionNew = "";
   1150         var lines = yellow.toolbox.getTextLines(textSelection);
   1151         for (var i=0; i<lines.length; i++) {
   1152             var matches = lines[i].match(/^(\s*[\#\*\-\!\>\s]+)?(\s+\[.\]|\s*\d+\.)?[ \t]+/);
   1153             if (matches) {
   1154                 var attributesOnly = lines[i].match(/^\s*!\s*\{\./);
   1155                 if (!attributesOnly) textSelectionNew += lines[i].substring(matches[0].length);
   1156             } else {
   1157                 textSelectionNew += lines[i];
   1158             }
   1159         }
   1160         textSelection = textSelectionNew;
   1161         if (information.type.indexOf("remove")==-1) {
   1162             textSelectionNew = information.attributes;
   1163             var linePrefix = information.prefix;
   1164             lines = yellow.toolbox.getTextLines(textSelection.length!=0 ? textSelection : "\n");
   1165             for (var i=0; i<lines.length; i++) {
   1166                 textSelectionNew += linePrefix+lines[i];
   1167                 if (information.prefix=="1. ") {
   1168                     var matches = linePrefix.match(/^(\d+)\.\s/);
   1169                     if (matches) linePrefix = (parseInt(matches[1])+1)+". ";
   1170                 }
   1171             }
   1172             textSelection = textSelectionNew;
   1173         }
   1174         return textSelection;
   1175     },
   1176     
   1177     // Return Markdown for fenced block
   1178     getMarkdownFencedBlock: function(textSelection, information) {
   1179         var textSelectionNew = "";
   1180         var lines = yellow.toolbox.getTextLines(textSelection);
   1181         for (var i=0; i<lines.length; i++) {
   1182             var matches = lines[i].match(/^```/);
   1183             if (!matches) textSelectionNew += lines[i];
   1184         }
   1185         textSelection = textSelectionNew;
   1186         if (information.type.indexOf("remove")==-1) {
   1187             if (textSelection.length==0) textSelection = "\n";
   1188             textSelection = information.prefix + textSelection + information.prefix;
   1189         }
   1190         return textSelection;
   1191     },
   1192     
   1193     // Return Markdown for link
   1194     getMarkdownLink: function(textSelection, information) {
   1195         return textSelection.length!=0 ? information.prefix.replace("link", textSelection) : information.prefix;
   1196     },
   1197     
   1198     // Set meta data
   1199     setMetaData: function(element, key, toggle) {
   1200         var information = this.getMetaDataInformation(element, key);
   1201         if (information.bottom!=0) {
   1202             var value = "";
   1203             if (key=="status") {
   1204                 var tokens = yellow.system.editStatusValues.split(/\s*,\s*/);
   1205                 var index = tokens.indexOf(information.value);
   1206                 value = tokens[index+1<tokens.length ? index+1 : index];
   1207             }
   1208             var selectionStart = information.found ? information.start : information.bottom;
   1209             var selectionEnd = information.found ? information.end : information.bottom;
   1210             var text = information.text;
   1211             var textSelectionBefore = text.substring(0, selectionStart);
   1212             var textSelection = text.substring(selectionStart, selectionEnd);
   1213             var textSelectionAfter = text.substring(selectionEnd, text.length);
   1214             var textSelectionNew = yellow.toolbox.toUpperFirst(key)+": "+value+"\n";
   1215             if (information.found && information.value==value && toggle) textSelectionNew = "";
   1216             var selectionStartNew = selectionStart;
   1217             var selectionEndNew = selectionStart + textSelectionNew.trim().length;
   1218             element.focus();
   1219             element.setSelectionRange(selectionStart, selectionEnd);
   1220             document.execCommand("insertText", false, textSelectionNew);
   1221             element.value = textSelectionBefore + textSelectionNew + textSelectionAfter;
   1222             element.setSelectionRange(selectionStartNew, selectionEndNew);
   1223             element.scrollTop = 0;
   1224             if (yellow.system.coreDebugMode) console.log("yellow.editor.setMetaData key:"+key);
   1225         }
   1226     },
   1227     
   1228     // Return meta data information
   1229     getMetaDataInformation: function(element, key) {
   1230         var text = element.value;
   1231         var value = "";
   1232         var start = 0, end = 0, top = 0, bottom = 0;
   1233         var found = false;
   1234         var parts = text.match(/^(\xEF\xBB\xBF)?(\-\-\-[\r\n]+)([\s\S]+?)\-\-\-[\r\n]+/);
   1235         if (parts) {
   1236             key = yellow.toolbox.toLowerFirst(key);
   1237             start = end = top = ((parts[1] ? parts[1] : "")+parts[2]).length;
   1238             bottom = ((parts[1] ? parts[1] : "")+parts[2]+parts[3]).length;
   1239             var lines = yellow.toolbox.getTextLines(parts[3]);
   1240             for (var i=0; i<lines.length; i++) {
   1241                 var matches = lines[i].match(/^\s*(.*?)\s*:\s*(.*?)\s*$/);
   1242                 if (matches && yellow.toolbox.toLowerFirst(matches[1])==key && matches[2].length!=0) {
   1243                     value = matches[2];
   1244                     end = start + lines[i].length;
   1245                     found = true;
   1246                     break;
   1247                 }
   1248                 start = end = start + lines[i].length;
   1249             }
   1250         }
   1251         return { "text":text, "value":value, "start":start, "end":end, "top":top, "bottom":bottom, "found":found };
   1252     },
   1253     
   1254     // Replace text
   1255     replace: function(element, textOld, textNew) {
   1256         var text = element.value;
   1257         var selectionStart = element.selectionStart;
   1258         var selectionEnd = element.selectionEnd;
   1259         var selectionStartFound = text.indexOf(textOld);
   1260         var selectionEndFound = selectionStartFound + textOld.length;
   1261         if (selectionStartFound!=-1) {
   1262             var selectionStartNew = selectionStart<selectionStartFound ? selectionStart : selectionStart+textNew.length-textOld.length;
   1263             var selectionEndNew = selectionEnd<selectionEndFound ? selectionEnd : selectionEnd+textNew.length-textOld.length;
   1264             var textBefore = text.substring(0, selectionStartFound);
   1265             var textAfter = text.substring(selectionEndFound, text.length);
   1266             if (textOld!=textNew) {
   1267                 element.focus();
   1268                 element.setSelectionRange(selectionStartFound, selectionEndFound);
   1269                 document.execCommand("insertText", false, textNew);
   1270                 element.value = textBefore + textNew + textAfter;
   1271                 element.setSelectionRange(selectionStartNew, selectionEndNew);
   1272             }
   1273         }
   1274     },
   1275     
   1276     // Undo changes
   1277     undo: function() {
   1278         document.execCommand("undo");
   1279     },
   1280 
   1281     // Redo changes
   1282     redo: function() {
   1283         document.execCommand("redo");
   1284     }
   1285 };
   1286 
   1287 yellow.toolbox = {
   1288 
   1289     // Insert element before reference element
   1290     insertBefore: function(element, elementReference) {
   1291         elementReference.parentNode.insertBefore(element, elementReference);
   1292     },
   1293 
   1294     // Insert element after reference element
   1295     insertAfter: function(element, elementReference) {
   1296         elementReference.parentNode.insertBefore(element, elementReference.nextSibling);
   1297     },
   1298 
   1299     // Add element class
   1300     addClass: function(element, name) {
   1301         element.classList.add(name);
   1302     },
   1303     
   1304     // Remove element class
   1305     removeClass: function(element, name) {
   1306         element.classList.remove(name);
   1307     },
   1308 
   1309     // Add attribute information
   1310     addValue: function(selector, name, value) {
   1311         var element = document.querySelector(selector);
   1312         element.setAttribute(name, element.getAttribute(name) + value);
   1313     },
   1314 
   1315     // Remove attribute information
   1316     removeValue: function(selector, name, value) {
   1317         var element = document.querySelector(selector);
   1318         element.setAttribute(name, element.getAttribute(name).replace(value, ""));
   1319     },
   1320     
   1321     // Add event handler
   1322     addEvent: function(element, type, handler) {
   1323         element.addEventListener(type, handler, false);
   1324     },
   1325     
   1326     // Remove event handler
   1327     removeEvent: function(element, type, handler) {
   1328         element.removeEventListener(type, handler, false);
   1329     },
   1330     
   1331     // Return shortcut from keyboard event, alphanumeric only
   1332     getEventShortcut: function(e) {
   1333         var shortcut = "";
   1334         if (e.keyCode>=48 && e.keyCode<=90) {
   1335             shortcut += (e.ctrlKey ? "ctrl+" : "")+(e.metaKey ? "meta+" : "")+(e.altKey ? "alt+" : "")+(e.shiftKey ? "shift+" : "");
   1336             shortcut += String.fromCharCode(e.keyCode).toLowerCase();
   1337         }
   1338         return shortcut;
   1339     },
   1340     
   1341     // Return element width in pixel
   1342     getWidth: function(element) {
   1343         return element.offsetWidth - this.getBoxSize(element).width;
   1344     },
   1345     
   1346     // Return element height in pixel
   1347     getHeight: function(element) {
   1348         return element.offsetHeight - this.getBoxSize(element).height;
   1349     },
   1350     
   1351     // Set element width in pixel, including padding and border
   1352     setOuterWidth: function(element, width) {
   1353         element.style.width = Math.max(0, width - this.getBoxSize(element).width) + "px";
   1354     },
   1355     
   1356     // Set element height in pixel, including padding and border
   1357     setOuterHeight: function(element, height) {
   1358         element.style.height = Math.max(0, height - this.getBoxSize(element).height) + "px";
   1359     },
   1360     
   1361     // Return element width in pixel, including padding and border
   1362     getOuterWidth: function(element, includeMargin) {
   1363         var width = element.offsetWidth;
   1364         if (includeMargin) width += this.getMarginSize(element).width;
   1365         return width;
   1366     },
   1367 
   1368     // Return element height in pixel, including padding and border
   1369     getOuterHeight: function(element, includeMargin) {
   1370         var height = element.offsetHeight;
   1371         if (includeMargin) height += this.getMarginSize(element).height;
   1372         return height;
   1373     },
   1374     
   1375     // Set element left position in pixel
   1376     setOuterLeft: function(element, left) {
   1377         element.style.left = Math.max(0, left) + "px";
   1378     },
   1379     
   1380     // Set element top position in pixel
   1381     setOuterTop: function(element, top) {
   1382         element.style.top = Math.max(0, top) + "px";
   1383     },
   1384     
   1385     // Return element left position in pixel
   1386     getOuterLeft: function(element) {
   1387         return element.getBoundingClientRect().left + window.pageXOffset;
   1388     },
   1389     
   1390     // Return element top position in pixel
   1391     getOuterTop: function(element) {
   1392         return element.getBoundingClientRect().top + window.pageYOffset;
   1393     },
   1394     
   1395     // Return window width in pixel
   1396     getWindowWidth: function() {
   1397         return window.innerWidth;
   1398     },
   1399     
   1400     // Return window height in pixel
   1401     getWindowHeight: function() {
   1402         return window.innerHeight;
   1403     },
   1404     
   1405     // Return element CSS property
   1406     getStyle: function(element, property) {
   1407         return window.getComputedStyle(element).getPropertyValue(property);
   1408     },
   1409     
   1410     // Return element CSS padding and border
   1411     getBoxSize: function(element) {
   1412         var paddingLeft = parseFloat(this.getStyle(element, "padding-left")) || 0;
   1413         var paddingRight = parseFloat(this.getStyle(element, "padding-right")) || 0;
   1414         var borderLeft = parseFloat(this.getStyle(element, "border-left-width")) || 0;
   1415         var borderRight = parseFloat(this.getStyle(element, "border-right-width")) || 0;
   1416         var width = paddingLeft + paddingRight + borderLeft + borderRight;
   1417         var paddingTop = parseFloat(this.getStyle(element, "padding-top")) || 0;
   1418         var paddingBottom = parseFloat(this.getStyle(element, "padding-bottom")) || 0;
   1419         var borderTop = parseFloat(this.getStyle(element, "border-top-width")) || 0;
   1420         var borderBottom = parseFloat(this.getStyle(element, "border-bottom-width")) || 0;
   1421         var height = paddingTop + paddingBottom + borderTop + borderBottom;
   1422         return { "width":width, "height":height };
   1423     },
   1424     
   1425     // Return element CSS margin
   1426     getMarginSize: function(element) {
   1427         var marginLeft = parseFloat(this.getStyle(element, "margin-left")) || 0;
   1428         var marginRight = parseFloat(this.getStyle(element, "margin-right")) || 0;
   1429         var width = marginLeft + marginRight;
   1430         var marginTop = parseFloat(this.getStyle(element, "margin-top")) || 0;
   1431         var marginBottom = parseFloat(this.getStyle(element, "margin-bottom")) || 0;
   1432         var height = marginTop + marginBottom;
   1433         return { "width":width, "height":height };
   1434     },
   1435     
   1436     // Set element visibility
   1437     setVisible: function(element, show, fadeout) {
   1438         if (fadeout && !show) {
   1439             var opacity = 1;
   1440             function renderFrame() {
   1441                 opacity -= .1;
   1442                 if (opacity<=0) {
   1443                     element.style.opacity = "initial";
   1444                     element.style.display = "none";
   1445                 } else {
   1446                     element.style.opacity = opacity;
   1447                     requestAnimationFrame(renderFrame);
   1448                 }
   1449             }
   1450             renderFrame();
   1451         } else {
   1452             element.style.display = show ? "block" : "none";
   1453         }
   1454     },
   1455 
   1456     // Check if element exists and is visible
   1457     isVisible: function(element) {
   1458         return element && element.style.display!="none";
   1459     },
   1460     
   1461     // Convert first letter to lowercase
   1462     toLowerFirst: function(string) {
   1463         return string.charAt(0).toLowerCase()+string.slice(1);
   1464     },
   1465 
   1466     // Convert first letter to uppercase
   1467     toUpperFirst: function(string) {
   1468         return string.charAt(0).toUpperCase()+string.slice(1);
   1469     },
   1470     
   1471     // Return lines from text string, including newline
   1472     getTextLines: function(string) {
   1473         var lines = string.split("\n");
   1474         for (var i=0; i<lines.length; i++) lines[i] = lines[i]+"\n";
   1475         if (string.length==0 || string.charAt(string.length-1)=="\n") lines.pop();
   1476         return lines;
   1477     },
   1478     
   1479     // Return browser cookie
   1480     getCookie: function(key) {
   1481         var matches = document.cookie.match("(^|; )"+key+"=([^;]+)");
   1482         return matches ? unescape(matches[2]) : "";
   1483     },
   1484     
   1485     // Encode HTML special characters
   1486     encodeHtml: function(string) {
   1487         return string
   1488             .replace(/&/g, "&amp;")
   1489             .replace(/</g, "&lt;")
   1490             .replace(/>/g, "&gt;")
   1491             .replace(/"/g, "&quot;");
   1492     },
   1493     
   1494     // Submit form with post method
   1495     submitForm: function(arguments) {
   1496         var elementForm = document.createElement("form");
   1497         elementForm.setAttribute("method", "post");
   1498         for (var key in arguments) {
   1499             if (!arguments.hasOwnProperty(key)) continue;
   1500             var elementInput = document.createElement("input");
   1501             elementInput.setAttribute("type", "hidden");
   1502             elementInput.setAttribute("name", key);
   1503             elementInput.setAttribute("value", arguments[key]);
   1504             elementForm.appendChild(elementInput);
   1505         }
   1506         document.body.appendChild(elementForm);
   1507         elementForm.submit();
   1508     }
   1509 };
   1510 
   1511 yellow.edit.intervalId = setInterval("yellow.onLoad(new Event('DOMContentLoading'))", 1);
   1512 window.addEventListener("DOMContentLoaded", yellow.onLoad, false);