diff --git a/CHANGELOG.md b/CHANGELOG.md index a505f09..5c82969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### v2.0.4 +- Fix pasting markdown and html +- Add pasting of images (with autoupload) + ### v2.0.3 - Fixed an issue, where links are converted wrong diff --git a/composer.json b/composer.json index f5edccb..ef8e58f 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "homepage": "https://github.com/Offerel/roundcube_primitivenotes", "type": "roundcube-plugin", "license": "AGPL-3.0", - "version": "2.0.3", + "version": "2.0.4", "authors": [ { "name": "Offerel", diff --git a/js/notes.js b/js/notes.js index ee6e8bf..4dfaa68 100644 --- a/js/notes.js +++ b/js/notes.js @@ -1,7 +1,7 @@ /** * Roundcube Notes Plugin * - * @version 2.0.3 + * @version 2.0.4 * @author Offerel * @copyright Copyright (c) 2021, Offerel * @license GNU General Public License, version 3 @@ -23,7 +23,9 @@ $(document).ready(function(){ delimiters: ',|;| ', placeholder: 'Tags' }); - + + var editor1 = document.getElementById("editor1"); + $.ajax({ 'type': "POST", 'url': "notes.php", @@ -39,9 +41,9 @@ $(document).ready(function(){ let cookie = element.split('='); if(cookie[0].indexOf('pn_') > 0) media_folder = JSON.parse(decodeURIComponent(cookie[1])); }); - + var mde = new EasyMDE({ - element: document.getElementById('editor1'), + element: editor1, autoDownloadFontAwesome: false, autofocus: true, previewImagesInEditor: false, @@ -51,8 +53,8 @@ $(document).ready(function(){ promptURLs: true, inputStyle: 'contenteditable', nativeSpellcheck: true, - forceSync: true, - //sideBySideFullscreen: false, + forceSync: false, + sideBySideFullscreen: true, renderingConfig: { codeSyntaxHighlighting: true, sanitizerFunction: function(renderedHTML) { @@ -61,7 +63,7 @@ $(document).ready(function(){ }, }, toolbar: [{ name: 'Save', - action: saveFile, + action: saveFile, className: 'fa fa-floppy-o', title: 'Save', }, '|', @@ -69,12 +71,12 @@ $(document).ready(function(){ 'code', 'quote', 'unordered-list', 'ordered-list', '|', 'link', { name: 'Image', - action: uplInsertImage, + action: uplInsertImage, className: 'fa fa-picture-o', title: 'Add image from URL', }, { name: 'Image', - action: uplLocalImage, + action: uplLocalImage, className: 'fa fa-file-image-o', title: 'Upload and insert local image', }, @@ -111,7 +113,6 @@ $(document).ready(function(){ document.getElementById('author').value = ''; document.getElementById('date').value = ''; document.getElementById('source').value = ''; - document.querySelector('#main_area .EasyMDEContainer').addEventListener('paste', pasteParse, false); } else { let toolbar = document.createElement('div'); toolbar.id = 'atoolbar'; @@ -166,7 +167,7 @@ $(document).ready(function(){ } } }); - + document.addEventListener("keyup", event => { if(event.key == 'Escape') { if(document.getElementById('estate').value == 'e') { @@ -185,7 +186,6 @@ $(document).ready(function(){ }); document.getElementById('notesearch').addEventListener('keyup', searchList, false); - document.getElementById('save_button').addEventListener('click', function() { document.getElementById('metah').submit(); }); @@ -194,42 +194,104 @@ $(document).ready(function(){ new rcube_splitter({ id:'notessplitter', p1:'#sidebar', p2:'#main', orientation:'v', relative:true, start:400, min:250, size:12 }).init(); + document.querySelector('.EasyMDEContainer').addEventListener('paste', pasteParse, true); + function pasteParse(event) { - const pastedText = event.clipboardData.getData('text'); - const pastedHTML = event.clipboardData.getData('text/html'); - let textArr = pastedText.split('\n'); - if(textArr[0] == '---') { - let cstart = pastedText.indexOf('---',4) + 3; - for(var i = 1; i < 10; i++) { - if(textArr[i] == '---') break; - let yentry = textArr[i].split(':'); + event.preventDefault(); + event.stopPropagation(); + + const pastedString = event.clipboardData.getData('text/html') || event.clipboardData.getData('text/plain'); + + for (var i = 0; i < event.clipboardData.items.length ; i++) { + let item = event.clipboardData.items[i]; + if(item.type.indexOf("image") != -1) { + let imageT = event.clipboardData.getData('text/html'); + if(imageT.indexOf('alt="') >= 0) { + let altS = imageT.indexOf('alt="') + 5; + let altE = imageT.indexOf('"',altS); + var alt = imageT.substr(altS, altE - altS); + } else var alt = ''; + + if(imageT.indexOf('title="') >= 0) { + let titleS = imageT.indexOf('title="') + 7; + let titleE = imageT.indexOf('"',titleS); + var title = imageT.substr(titleS, titleE - titleS); + } else var title = ''; + let loader = document.createElement("div"); + + loader.classList.add("db-spinner"); + loader.id = "db-spinner"; + document.getElementById("main").appendChild(loader); + + uploadFile(item.getAsFile(), alt, title); + return false; + } + } + + function uploadFile(file, alt, title) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.status == 200) { + if(title) title = ' "'+title+'"'; + mde.codemirror.replaceSelection('!['+alt+']('+xhr.responseText+title+')'); + document.getElementById("db-spinner").remove(); + } else { + console.log("Error! Upload failed"); + } + }; + + xhr.onerror = function() { + console.log("Error! Upload failed. Can not connect to server."); + }; + + var formData = new FormData(); + formData.append("localFile", file); + xhr.open('POST', 'notes.php', true); + xhr.send(formData); + } + + let options = { + headingStyle: 'atx', + hr: '-', + bulletListMarker: '-', + codeBlockStyle: 'fenced', + fence: '```', + emDelimiter: '*', + strongDelimiter: '**', + linkStyle: 'inlined', + linkReferenceStyle: 'full', + collapseMultipleWhitespaces: true, + preformattedCode: true, + }; + + let turndownService = new window.TurndownService(options); + + turndownService.addRule('kbd',{ + filter:['kbd'], + replacement: function(content) { + return '' + content + ''; + } + }); + + let markdownString = pastedString.startsWith('') ? turndownService.turndown(pastedString) : pastedString; + + if(markdownString.startsWith('---')) { + let mdArr = markdownString.split('\n'); + let cstart = markdownString.indexOf('---',4) + 3; + for(let i = 1; i < 10; i++) { + if(mdArr[i] == '---') break; + let yentry = mdArr[i].split(':'); if(yentry[0] == 'title') document.getElementById('note_name').value = yentry[1].trim(); if(yentry[0] == 'tags') tagify.addTags(yentry[1]); if(yentry[0] == 'author') document.getElementById('author').value = yentry[1].trim(); if(yentry[0] == 'date') document.getElementById('date').value = yentry.slice(1).join(':').trim(); + if(yentry[0] == 'updated') document.getElementById('updated').value = yentry.slice(1).join(':').trim(); if(yentry[0] == 'source') document.getElementById('source').value = yentry.slice(1).join(':').trim(); } - mde.value(pastedText.substr(cstart).trim()); - } - - if(pastedHTML) { - var options = { - headingStyle: 'atx', - hr: '-', - bulletListMarker: '-', - codeBlockStyle: 'fenced', - fence: '```', - emDelimiter: '*', - strongDelimiter: '**', - linkStyle: 'inlined', - linkReferenceStyle: 'full', - collapseMultipleWhitespaces: true, - preformattedCode: true, - }; - var turndownService = new window.TurndownService(options); - turndownService.keep(['kbd', 'ins']); - mde.value(turndownService.turndown(pastedHTML)); + markdownString = markdownString.substr(cstart).trim(); } + + mde.codemirror.replaceSelection(markdownString); } function firstNote() { diff --git a/js/notes.min.js b/js/notes.min.js index 7f38217..49b9fd7 100644 --- a/js/notes.min.js +++ b/js/notes.min.js @@ -1,9 +1,9 @@ /** * Roundcube Notes Plugin * - * @version 2.0.3 + * @version 2.0.4 * @author Offerel * @copyright Copyright (c) 2021, Offerel * @license GNU General Public License, version 3 */ -$(document).ready((function(){var tagify=new Tagify(document.querySelector("#ntags"),{whitelist:[],dropdown:{classname:"color-blue",enabled:0,maxItems:0,position:"text",closeOnSelect:!1,highlightFirst:!0},trim:!0,duplicates:!1,enforceWhitelist:!1,delimiters:",|;| ",placeholder:"Tags"});let cookiesArr;var media_folder;$.ajax({type:"POST",url:"notes.php",data:{action:"getTags"},success:function(data){tagify.settings.whitelist=JSON.parse(data)}}),document.cookie.split(";").forEach((function(element){let cookie=element.split("=");cookie[0].indexOf("pn_")>0&&(media_folder=JSON.parse(decodeURIComponent(cookie[1])))}));var mde=new EasyMDE({element:document.getElementById("editor1"),autoDownloadFontAwesome:!1,autofocus:!0,previewImagesInEditor:!1,spellChecker:!1,autofocus:!0,status:!1,promptURLs:!0,inputStyle:"contenteditable",nativeSpellcheck:!0,forceSync:!0,renderingConfig:{codeSyntaxHighlighting:!0,sanitizerFunction:function(renderedHTML){let output;return renderedHTML.replaceAll(media_folder,"notes.php?blink=")}},toolbar:[{name:"Save",action:saveFile,className:"fa fa-floppy-o",title:"Save"},"|","undo","redo","|","bold","italic","strikethrough","clean-block","|","heading","heading-smaller","heading-bigger","|","code","quote","unordered-list","ordered-list","|","link",{name:"Image",action:uplInsertImage,className:"fa fa-picture-o",title:"Add image from URL"},{name:"Image",action:uplLocalImage,className:"fa fa-file-image-o",title:"Upload and insert local image"},"table","|","preview","side-by-side","fullscreen","guide","|"]});function pasteParse(event){const pastedText=event.clipboardData.getData("text"),pastedHTML=event.clipboardData.getData("text/html");let textArr=pastedText.split("\n");if("---"==textArr[0]){let cstart=pastedText.indexOf("---",4)+3;for(var i=1;i<10&&"---"!=textArr[i];i++){let yentry=textArr[i].split(":");"title"==yentry[0]&&(document.getElementById("note_name").value=yentry[1].trim()),"tags"==yentry[0]&&tagify.addTags(yentry[1]),"author"==yentry[0]&&(document.getElementById("author").value=yentry[1].trim()),"date"==yentry[0]&&(document.getElementById("date").value=yentry.slice(1).join(":").trim()),"source"==yentry[0]&&(document.getElementById("source").value=yentry.slice(1).join(":").trim())}mde.value(pastedText.substr(cstart).trim())}if(pastedHTML){var options={headingStyle:"atx",hr:"-",bulletListMarker:"-",codeBlockStyle:"fenced",fence:"```",emDelimiter:"*",strongDelimiter:"**",linkStyle:"inlined",linkReferenceStyle:"full",collapseMultipleWhitespaces:!0,preformattedCode:!0},turndownService=new window.TurndownService(options);turndownService.keep(["kbd","ins"]),mde.value(turndownService.turndown(pastedHTML))}}function firstNote(){showNote(document.getElementById("filelist").firstElementChild.classList[0])}function sbfile(){let tags=tagify.value,tArr=[];for(let tag in tags)tArr.push(tags[tag].value);$.ajax({type:"POST",url:"notes.php",data:{action:"sbfile",name:document.getElementById("note_name").value,fname:document.getElementById("fname").value,tags:tArr,content:document.getElementById("editor1").value},success:function(response){""==response?(console.log("Note saved successfully"),location.reload()):alert(response)}})}function showNote(id){document.querySelector("#main_area .editor-toolbar").style.display="none",document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove(),document.getElementById("tbutton")&&document.getElementById("tbutton").remove();let loader=document.createElement("div");loader.classList.add("db-spinner"),loader.id="db-spinner",document.getElementById("main").appendChild(loader),document.getElementById("save_button").style.display="none";for(var elements=document.getElementsByClassName("selected");elements.length>0;)elements[0].classList.remove("selected");document.getElementById(id).classList.add("selected"),window.parent.document.getElementById("editnote").classList.remove("disabled"),window.parent.document.getElementById("deletenote").classList.remove("disabled"),window.parent.document.getElementById("sendnote").classList.remove("disabled");var fname=document.getElementById("entry"+id).value;$.ajax({type:"POST",url:"notes.php",data:{action:"showNote",filename:fname,id:id},success:function(data){var note=JSON.parse(data);document.getElementById("bcontent")&&document.getElementById("bcontent").remove(),document.querySelector(".EasyMDEContainer").classList.remove("EasyMDEContainerH"),document.getElementById("tocdiv")&&document.getElementById("tocdiv").remove();let headerTitle=document.createElement("span");if(headerTitle.id="headerTitle",headerTitle.classList.add("headerTitle"),document.querySelector("#main_header #note_name")&&document.querySelector("#main_header #note_name").replaceWith(headerTitle),document.querySelector("tags").classList.remove("edit"),document.getElementById("headerTitle").innerText=note.notename,document.getElementById("fname").value=note.filename,document.getElementById("author").value=note.author,document.getElementById("date").value=note.date,document.getElementById("source").value=note.source,tagify.setReadonly(!0),tagify.removeAllTags(),tagify.addTags(note.tags),document.querySelector(".EasyMDEContainer").style="display: block;",document.getElementById("editor1").style="display none;",document.getElementById("editor1").value=note.content,mde.value(note.content),"text"==note.mime_type.substr(0,4)){"e"==document.getElementById("estate").value&&(document.getElementById("estate").value="s",mde.togglePreview());var headings=document.querySelectorAll("h1, h2, h3, h4, h5, h6");if(headings.length>0){let tbutton=document.createElement("button");tbutton.id="tbutton",tbutton.innerText="ToC",document.getElementById("main_header").appendChild(tbutton);let tocdiv=document.createElement("div");tocdiv.id="tocdiv";let o=0,a=0,list="c%";headings.forEach((function(element){a=element.tagName.substr(1,1),list=o"):o>a?list.replace("c%",'
  • '+element.innerText+"
  • c%"):list.replace("c%",'
  • '+element.innerText+"
  • c%"),o=a})),list=list.replace("c%",""),tocdiv.innerHTML=list,tbutton.addEventListener("click",(function(e){e.preventDefault(),tocdiv.classList.toggle("tdhidden")})),document.querySelector(".EasyMDEContainer").appendChild(tocdiv),document.querySelectorAll("#tocdiv a").forEach((function(elem){elem.addEventListener("click",(function(){tocdiv.classList.toggle("tdhidden")}))}))}}else{let bcontent=document.createElement("object");bcontent.data="data:"+note.mime_type+";base64,"+note.content,bcontent.type=note.mime_type,bcontent.id="bcontent",note.mime_type.includes("pdf")&&(bcontent.style="width: 100%; height: 100%;"),document.querySelector(".EasyMDEContainer").classList.add("EasyMDEContainerH"),document.getElementById("main_area").appendChild(bcontent),document.getElementById("editor1").style="display: none",document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove()}document.getElementById("db-spinner").parentNode.removeChild(loader)}})}function simage(){for(var allowed_extensions=new Array("jpg","jpeg","png"),file_extension=document.getElementById("localimg").value.split(".").pop().toLowerCase(),i=0;i<=allowed_extensions.length;i++)if(allowed_extensions[i]==file_extension){var file_data=$("#localimg").prop("files")[0],formData=new FormData;return formData.append("localFile",file_data),$.ajax({type:"POST",url:"notes.php",dataType:"text",cache:!1,contentType:!1,processData:!1,data:formData,success:function(data){pos=mde.codemirror.getCursor(),mde.codemirror.setSelection(pos,pos),mde.codemirror.replaceSelection("![]("+data+")")}}),!0}return alert("Unsupported file format"),!1}function saveFile(editor){let loader=document.createElement("div");loader.classList.add("db-spinner"),loader.id="db-spinner",document.getElementById("main").appendChild(loader);let fname=document.getElementById("fname").value,extb=fname.lastIndexOf(".")+1,tags=tagify.value,tArr=[];for(let tag in tags)tArr.push(tags[tag].value);$.ajax({type:"POST",url:"notes.php",data:{action:"editNote",note_name:document.getElementById("note_name").value,fname:fname,ntags:tArr,editor1:mde.value(),ftype:fname.substr(extb),author:document.getElementById("author").value,date:document.getElementById("date").value,source:document.getElementById("source").value},success:function(response){""==response?(console.log("Note saved successfully"),location.reload()):alert(response),loader.remove()}})}function uplLocalImage(){document.getElementById("localimg").click()}function uplInsertImage(){var imageURL=prompt("URL of the image","");if(!imageURL)return!1;$.ajax({type:"POST",url:"notes.php",data:{action:"uplImage",imageURL:imageURL},success:function(data){pos=mde.codemirror.getCursor(),mde.codemirror.setSelection(pos,pos),mde.codemirror.replaceSelection("![]("+data+")")}})}function searchList(){var input,filter,ul,li,a,i;for(filter=(input=document.getElementById("notesearch")).value.toUpperCase(),li=(ul=document.getElementById("filelist")).getElementsByTagName("li"),i=0;i-1?li[i].style.display="":li[i].style.display="none"}document.querySelectorAll("#filelist li a").forEach((function(note){note.addEventListener("click",(function(){showNote(note.parentElement.id),tagify.setReadonly(!0)}))})),window.addEventListener("message",e=>{let estate=document.getElementById("estate");if("tstate"in e.data&&tagify.setReadonly(e.data.tstate),"ttags"in e.data&&""==e.data.ttags&&tagify.removeAllTags(),"editor"in e.data&&"new"==e.data.editor){"s"==estate.value&&(mde.togglePreview(),estate.value="e"),mde.value(""),document.getElementById("fname").value="";let editor1=document.getElementById("editor1");if(editor1.value="","md"==e.data.format)document.querySelector("#main_area .editor-toolbar").style.display="block",document.querySelector(".EasyMDEContainer").style="display: block",mde.value(""),editor1.style="display: none;",document.getElementById("author").value="",document.getElementById("date").value="",document.getElementById("source").value="",document.querySelector("#main_area .EasyMDEContainer").addEventListener("paste",pasteParse,!1);else{let toolbar=document.createElement("div");toolbar.id="atoolbar";let bSave=document.createElement("li");bSave.id="bSave",bSave.classList.add("fa","fa-floppy-o"),bSave.addEventListener("click",sbfile,!1),toolbar.appendChild(bSave);let bSeperator=document.createElement("i");bSeperator.classList.add("separator"),toolbar.appendChild(bSeperator),editor1.parentNode.insertBefore(toolbar,editor1),document.querySelector(".EasyMDEContainer").style="display: none",editor1.style="display: block"}}if("editor"in e.data&&"edit"==e.data.editor){"s"==estate.value&&(mde.togglePreview(),estate.value="e");let file=document.getElementById("fname").value.split("."),format=file[file.length-1];switch(document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove(),format){case"md":document.querySelector(".EasyMDEContainer").style="display: block;",document.querySelector("#main_area .editor-toolbar").style.display="block",document.querySelector("#editor1").style="display: none;";break;default:let toolbar=document.createElement("div"),editor=document.getElementById("editor1");toolbar.id="atoolbar";let bSave=document.createElement("li");bSave.id="bSave",bSave.classList.add("fa","fa-floppy-o"),bSave.addEventListener("click",sbfile,!1),toolbar.appendChild(bSave);let bSeperator=document.createElement("i");bSeperator.classList.add("separator"),toolbar.appendChild(bSeperator),editor.parentNode.insertBefore(toolbar,editor),document.querySelector(".EasyMDEContainer").style="display: none",editor.style="display: block",document.getElementById("bcontent")&&(document.getElementById("editor1").style="display: none")}}}),document.addEventListener("keyup",event=>{if("Escape"==event.key&&"e"==document.getElementById("estate").value){mde.togglePreview(),document.querySelector("#main_area .editor-toolbar").style.display="none",document.getElementById("estate").value="s";let headerTitle=document.createElement("span");headerTitle.id="headerTitle",headerTitle.classList.add("headerTitle"),headerTitle.innerText=document.getElementById("note_name").value,document.querySelector("#main_header #note_name").replaceWith(headerTitle),document.querySelector("tags").classList.remove("edit"),tagify.setReadonly(!0)}}),document.getElementById("notesearch").addEventListener("keyup",searchList,!1),document.getElementById("save_button").addEventListener("click",(function(){document.getElementById("metah").submit()})),document.getElementById("localimg").addEventListener("change",simage,!1),new rcube_splitter({id:"notessplitter",p1:"#sidebar",p2:"#main",orientation:"v",relative:!0,start:400,min:250,size:12}).init(),firstNote()})); \ No newline at end of file +$(document).ready((function(){var tagify=new Tagify(document.querySelector("#ntags"),{whitelist:[],dropdown:{classname:"color-blue",enabled:0,maxItems:0,position:"text",closeOnSelect:!1,highlightFirst:!0},trim:!0,duplicates:!1,enforceWhitelist:!1,delimiters:",|;| ",placeholder:"Tags"}),editor1=document.getElementById("editor1");let cookiesArr;var media_folder;$.ajax({type:"POST",url:"notes.php",data:{action:"getTags"},success:function(data){tagify.settings.whitelist=JSON.parse(data)}}),document.cookie.split(";").forEach((function(element){let cookie=element.split("=");cookie[0].indexOf("pn_")>0&&(media_folder=JSON.parse(decodeURIComponent(cookie[1])))}));var mde=new EasyMDE({element:editor1,autoDownloadFontAwesome:!1,autofocus:!0,previewImagesInEditor:!1,spellChecker:!1,autofocus:!0,status:!1,promptURLs:!0,inputStyle:"contenteditable",nativeSpellcheck:!0,forceSync:!1,sideBySideFullscreen:!0,renderingConfig:{codeSyntaxHighlighting:!0,sanitizerFunction:function(renderedHTML){let output;return renderedHTML.replaceAll(media_folder,"notes.php?blink=")}},toolbar:[{name:"Save",action:saveFile,className:"fa fa-floppy-o",title:"Save"},"|","undo","redo","|","bold","italic","strikethrough","clean-block","|","heading","heading-smaller","heading-bigger","|","code","quote","unordered-list","ordered-list","|","link",{name:"Image",action:uplInsertImage,className:"fa fa-picture-o",title:"Add image from URL"},{name:"Image",action:uplLocalImage,className:"fa fa-file-image-o",title:"Upload and insert local image"},"table","|","preview","side-by-side","fullscreen","guide","|"]});function pasteParse(event){event.preventDefault(),event.stopPropagation();const pastedString=event.clipboardData.getData("text/html")||event.clipboardData.getData("text/plain");for(var i=0;i=0){let altS=imageT.indexOf('alt="')+5,altE=imageT.indexOf('"',altS);var alt=imageT.substr(altS,altE-altS)}else var alt="";if(imageT.indexOf('title="')>=0){let titleS=imageT.indexOf('title="')+7,titleE=imageT.indexOf('"',titleS);var title=imageT.substr(titleS,titleE-titleS)}else var title="";let loader=document.createElement("div");return loader.classList.add("db-spinner"),loader.id="db-spinner",document.getElementById("main").appendChild(loader),uploadFile(item.getAsFile(),alt,title),!1}}function uploadFile(file,alt,title){var xhr=new XMLHttpRequest;xhr.onload=function(){200==xhr.status?(title&&(title=' "'+title+'"'),mde.codemirror.replaceSelection("!["+alt+"]("+xhr.responseText+title+")"),document.getElementById("db-spinner").remove()):console.log("Error! Upload failed")},xhr.onerror=function(){console.log("Error! Upload failed. Can not connect to server.")};var formData=new FormData;formData.append("localFile",file),xhr.open("POST","notes.php",!0),xhr.send(formData)}let options={headingStyle:"atx",hr:"-",bulletListMarker:"-",codeBlockStyle:"fenced",fence:"```",emDelimiter:"*",strongDelimiter:"**",linkStyle:"inlined",linkReferenceStyle:"full",collapseMultipleWhitespaces:!0,preformattedCode:!0},turndownService=new window.TurndownService(options);turndownService.addRule("kbd",{filter:["kbd"],replacement:function(content){return""+content+""}});let markdownString=pastedString.startsWith("")?turndownService.turndown(pastedString):pastedString;if(markdownString.startsWith("---")){let mdArr=markdownString.split("\n"),cstart=markdownString.indexOf("---",4)+3;for(let i=1;i<10&&"---"!=mdArr[i];i++){let yentry=mdArr[i].split(":");"title"==yentry[0]&&(document.getElementById("note_name").value=yentry[1].trim()),"tags"==yentry[0]&&tagify.addTags(yentry[1]),"author"==yentry[0]&&(document.getElementById("author").value=yentry[1].trim()),"date"==yentry[0]&&(document.getElementById("date").value=yentry.slice(1).join(":").trim()),"updated"==yentry[0]&&(document.getElementById("updated").value=yentry.slice(1).join(":").trim()),"source"==yentry[0]&&(document.getElementById("source").value=yentry.slice(1).join(":").trim())}markdownString=markdownString.substr(cstart).trim()}mde.codemirror.replaceSelection(markdownString)}function firstNote(){showNote(document.getElementById("filelist").firstElementChild.classList[0])}function sbfile(){let tags=tagify.value,tArr=[];for(let tag in tags)tArr.push(tags[tag].value);$.ajax({type:"POST",url:"notes.php",data:{action:"sbfile",name:document.getElementById("note_name").value,fname:document.getElementById("fname").value,tags:tArr,content:document.getElementById("editor1").value},success:function(response){""==response?(console.log("Note saved successfully"),location.reload()):alert(response)}})}function showNote(id){document.querySelector("#main_area .editor-toolbar").style.display="none",document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove(),document.getElementById("tbutton")&&document.getElementById("tbutton").remove();let loader=document.createElement("div");loader.classList.add("db-spinner"),loader.id="db-spinner",document.getElementById("main").appendChild(loader),document.getElementById("save_button").style.display="none";for(var elements=document.getElementsByClassName("selected");elements.length>0;)elements[0].classList.remove("selected");document.getElementById(id).classList.add("selected"),window.parent.document.getElementById("editnote").classList.remove("disabled"),window.parent.document.getElementById("deletenote").classList.remove("disabled"),window.parent.document.getElementById("sendnote").classList.remove("disabled");var fname=document.getElementById("entry"+id).value;$.ajax({type:"POST",url:"notes.php",data:{action:"showNote",filename:fname,id:id},success:function(data){var note=JSON.parse(data);document.getElementById("bcontent")&&document.getElementById("bcontent").remove(),document.querySelector(".EasyMDEContainer").classList.remove("EasyMDEContainerH"),document.getElementById("tocdiv")&&document.getElementById("tocdiv").remove();let headerTitle=document.createElement("span");if(headerTitle.id="headerTitle",headerTitle.classList.add("headerTitle"),document.querySelector("#main_header #note_name")&&document.querySelector("#main_header #note_name").replaceWith(headerTitle),document.querySelector("tags").classList.remove("edit"),document.getElementById("headerTitle").innerText=note.notename,document.getElementById("fname").value=note.filename,document.getElementById("author").value=note.author,document.getElementById("date").value=note.date,document.getElementById("source").value=note.source,tagify.setReadonly(!0),tagify.removeAllTags(),tagify.addTags(note.tags),document.querySelector(".EasyMDEContainer").style="display: block;",document.getElementById("editor1").style="display none;",document.getElementById("editor1").value=note.content,mde.value(note.content),"text"==note.mime_type.substr(0,4)){"e"==document.getElementById("estate").value&&(document.getElementById("estate").value="s",mde.togglePreview());var headings=document.querySelectorAll("h1, h2, h3, h4, h5, h6");if(headings.length>0){let tbutton=document.createElement("button");tbutton.id="tbutton",tbutton.innerText="ToC",document.getElementById("main_header").appendChild(tbutton);let tocdiv=document.createElement("div");tocdiv.id="tocdiv";let o=0,a=0,list="c%";headings.forEach((function(element){a=element.tagName.substr(1,1),list=o"):o>a?list.replace("c%",'
  • '+element.innerText+"
  • c%"):list.replace("c%",'
  • '+element.innerText+"
  • c%"),o=a})),list=list.replace("c%",""),tocdiv.innerHTML=list,tbutton.addEventListener("click",(function(e){e.preventDefault(),tocdiv.classList.toggle("tdhidden")})),document.querySelector(".EasyMDEContainer").appendChild(tocdiv),document.querySelectorAll("#tocdiv a").forEach((function(elem){elem.addEventListener("click",(function(){tocdiv.classList.toggle("tdhidden")}))}))}}else{let bcontent=document.createElement("object");bcontent.data="data:"+note.mime_type+";base64,"+note.content,bcontent.type=note.mime_type,bcontent.id="bcontent",note.mime_type.includes("pdf")&&(bcontent.style="width: 100%; height: 100%;"),document.querySelector(".EasyMDEContainer").classList.add("EasyMDEContainerH"),document.getElementById("main_area").appendChild(bcontent),document.getElementById("editor1").style="display: none",document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove()}document.getElementById("db-spinner").parentNode.removeChild(loader)}})}function simage(){for(var allowed_extensions=new Array("jpg","jpeg","png"),file_extension=document.getElementById("localimg").value.split(".").pop().toLowerCase(),i=0;i<=allowed_extensions.length;i++)if(allowed_extensions[i]==file_extension){var file_data=$("#localimg").prop("files")[0],formData=new FormData;return formData.append("localFile",file_data),$.ajax({type:"POST",url:"notes.php",dataType:"text",cache:!1,contentType:!1,processData:!1,data:formData,success:function(data){pos=mde.codemirror.getCursor(),mde.codemirror.setSelection(pos,pos),mde.codemirror.replaceSelection("![]("+data+")")}}),!0}return alert("Unsupported file format"),!1}function saveFile(editor){let loader=document.createElement("div");loader.classList.add("db-spinner"),loader.id="db-spinner",document.getElementById("main").appendChild(loader);let fname=document.getElementById("fname").value,extb=fname.lastIndexOf(".")+1,tags=tagify.value,tArr=[];for(let tag in tags)tArr.push(tags[tag].value);$.ajax({type:"POST",url:"notes.php",data:{action:"editNote",note_name:document.getElementById("note_name").value,fname:fname,ntags:tArr,editor1:mde.value(),ftype:fname.substr(extb),author:document.getElementById("author").value,date:document.getElementById("date").value,source:document.getElementById("source").value},success:function(response){""==response?(console.log("Note saved successfully"),location.reload()):alert(response),loader.remove()}})}function uplLocalImage(){document.getElementById("localimg").click()}function uplInsertImage(){var imageURL=prompt("URL of the image","");if(!imageURL)return!1;$.ajax({type:"POST",url:"notes.php",data:{action:"uplImage",imageURL:imageURL},success:function(data){pos=mde.codemirror.getCursor(),mde.codemirror.setSelection(pos,pos),mde.codemirror.replaceSelection("![]("+data+")")}})}function searchList(){var input,filter,ul,li,a,i;for(filter=(input=document.getElementById("notesearch")).value.toUpperCase(),li=(ul=document.getElementById("filelist")).getElementsByTagName("li"),i=0;i-1?li[i].style.display="":li[i].style.display="none"}document.querySelectorAll("#filelist li a").forEach((function(note){note.addEventListener("click",(function(){showNote(note.parentElement.id),tagify.setReadonly(!0)}))})),window.addEventListener("message",e=>{let estate=document.getElementById("estate");if("tstate"in e.data&&tagify.setReadonly(e.data.tstate),"ttags"in e.data&&""==e.data.ttags&&tagify.removeAllTags(),"editor"in e.data&&"new"==e.data.editor){"s"==estate.value&&(mde.togglePreview(),estate.value="e"),mde.value(""),document.getElementById("fname").value="";let editor1=document.getElementById("editor1");if(editor1.value="","md"==e.data.format)document.querySelector("#main_area .editor-toolbar").style.display="block",document.querySelector(".EasyMDEContainer").style="display: block",mde.value(""),editor1.style="display: none;",document.getElementById("author").value="",document.getElementById("date").value="",document.getElementById("source").value="";else{let toolbar=document.createElement("div");toolbar.id="atoolbar";let bSave=document.createElement("li");bSave.id="bSave",bSave.classList.add("fa","fa-floppy-o"),bSave.addEventListener("click",sbfile,!1),toolbar.appendChild(bSave);let bSeperator=document.createElement("i");bSeperator.classList.add("separator"),toolbar.appendChild(bSeperator),editor1.parentNode.insertBefore(toolbar,editor1),document.querySelector(".EasyMDEContainer").style="display: none",editor1.style="display: block"}}if("editor"in e.data&&"edit"==e.data.editor){"s"==estate.value&&(mde.togglePreview(),estate.value="e");let file=document.getElementById("fname").value.split("."),format=file[file.length-1];switch(document.getElementById("atoolbar")&&document.getElementById("atoolbar").remove(),format){case"md":document.querySelector(".EasyMDEContainer").style="display: block;",document.querySelector("#main_area .editor-toolbar").style.display="block",document.querySelector("#editor1").style="display: none;";break;default:let toolbar=document.createElement("div"),editor=document.getElementById("editor1");toolbar.id="atoolbar";let bSave=document.createElement("li");bSave.id="bSave",bSave.classList.add("fa","fa-floppy-o"),bSave.addEventListener("click",sbfile,!1),toolbar.appendChild(bSave);let bSeperator=document.createElement("i");bSeperator.classList.add("separator"),toolbar.appendChild(bSeperator),editor.parentNode.insertBefore(toolbar,editor),document.querySelector(".EasyMDEContainer").style="display: none",editor.style="display: block",document.getElementById("bcontent")&&(document.getElementById("editor1").style="display: none")}}}),document.addEventListener("keyup",event=>{if("Escape"==event.key&&"e"==document.getElementById("estate").value){mde.togglePreview(),document.querySelector("#main_area .editor-toolbar").style.display="none",document.getElementById("estate").value="s";let headerTitle=document.createElement("span");headerTitle.id="headerTitle",headerTitle.classList.add("headerTitle"),headerTitle.innerText=document.getElementById("note_name").value,document.querySelector("#main_header #note_name").replaceWith(headerTitle),document.querySelector("tags").classList.remove("edit"),tagify.setReadonly(!0)}}),document.getElementById("notesearch").addEventListener("keyup",searchList,!1),document.getElementById("save_button").addEventListener("click",(function(){document.getElementById("metah").submit()})),document.getElementById("localimg").addEventListener("change",simage,!1),new rcube_splitter({id:"notessplitter",p1:"#sidebar",p2:"#main",orientation:"v",relative:!0,start:400,min:250,size:12}).init(),document.querySelector(".EasyMDEContainer").addEventListener("paste",pasteParse,!0),firstNote()})); \ No newline at end of file diff --git a/js/primitivenotes.js b/js/primitivenotes.js index c091ef5..a01b252 100644 --- a/js/primitivenotes.js +++ b/js/primitivenotes.js @@ -1,7 +1,7 @@ /** * Roundcube Notes Plugin * - * @version 2.0.3 + * @version 2.0.4 * @author Offerel * @copyright Copyright (c) 2021, Offerel * @license GNU General Public License, version 3 diff --git a/js/primitivenotes.min.js b/js/primitivenotes.min.js index fe47099..fff32fc 100644 --- a/js/primitivenotes.min.js +++ b/js/primitivenotes.min.js @@ -1,7 +1,7 @@ /** * Roundcube Notes Plugin * - * @version 2.0.3 + * @version 2.0.4 * @author Offerel * @copyright Copyright (c) 2021, Offerel * @license GNU General Public License, version 3 diff --git a/js/turndown/turndown.js b/js/turndown/turndown.js index 716ff22..deb57cd 100644 --- a/js/turndown/turndown.js +++ b/js/turndown/turndown.js @@ -1,932 +1,956 @@ var TurndownService = (function () { -'use strict'; + 'use strict'; -function extend (destination) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (source.hasOwnProperty(key)) destination[key] = source[key]; + function extend (destination) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (source.hasOwnProperty(key)) destination[key] = source[key]; + } } + return destination } - return destination -} - -function repeat (character, count) { - return Array(count + 1).join(character) -} -var blockElements = [ - 'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas', - 'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption', - 'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', - 'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav', - 'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table', - 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul' -]; + function repeat (character, count) { + return Array(count + 1).join(character) + } -function isBlock (node) { - return blockElements.indexOf(node.nodeName.toLowerCase()) !== -1 -} + var blockElements = [ + 'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS', + 'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', + 'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', + 'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES', + 'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD', + 'TFOOT', 'TH', 'THEAD', 'TR', 'UL' + ]; + + function isBlock (node) { + return is(node, blockElements) + } -var voidElements = [ - 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', - 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' -]; + var voidElements = [ + 'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', + 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR' + ]; -function isVoid (node) { - return voidElements.indexOf(node.nodeName.toLowerCase()) !== -1 -} + function isVoid (node) { + return is(node, voidElements) + } -var voidSelector = voidElements.join(); -function hasVoid (node) { - return node.querySelector && node.querySelector(voidSelector) -} + function hasVoid (node) { + return has(node, voidElements) + } -var rules = {}; + var meaningfulWhenBlankElements = [ + 'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT', + 'AUDIO', 'VIDEO' + ]; -rules.paragraph = { - filter: 'p', + function isMeaningfulWhenBlank (node) { + return is(node, meaningfulWhenBlankElements) + } - replacement: function (content) { - return '\n\n' + content + '\n\n' + function hasMeaningfulWhenBlank (node) { + return has(node, meaningfulWhenBlankElements) } -}; -rules.lineBreak = { - filter: 'br', + function is (node, tagNames) { + return tagNames.indexOf(node.nodeName) >= 0 + } - replacement: function (content, node, options) { - return options.br + '\n' + function has (node, tagNames) { + return ( + node.getElementsByTagName && + tagNames.some(function (tagName) { + return node.getElementsByTagName(tagName).length + }) + ) } -}; -rules.heading = { - filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + var rules = {}; - replacement: function (content, node, options) { - var hLevel = Number(node.nodeName.charAt(1)); + rules.paragraph = { + filter: 'p', - if (options.headingStyle === 'setext' && hLevel < 3) { - var underline = repeat((hLevel === 1 ? '=' : '-'), content.length); - return ( - '\n\n' + content + '\n' + underline + '\n\n' - ) - } else { - return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n' + replacement: function (content) { + return '\n\n' + content + '\n\n' } - } -}; + }; -rules.blockquote = { - filter: 'blockquote', + rules.lineBreak = { + filter: 'br', - replacement: function (content) { - content = content.replace(/^\n+|\n+$/g, ''); - content = content.replace(/^/gm, '> '); - return '\n\n' + content + '\n\n' - } -}; + replacement: function (content, node, options) { + return options.br + '\n' + } + }; -rules.list = { - filter: ['ul', 'ol'], + rules.heading = { + filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], - replacement: function (content, node) { - var parent = node.parentNode; - if (parent.nodeName === 'LI' && parent.lastElementChild === node) { - return '\n' + content - } else { + replacement: function (content, node, options) { + var hLevel = Number(node.nodeName.charAt(1)); + + if (options.headingStyle === 'setext' && hLevel < 3) { + var underline = repeat((hLevel === 1 ? '=' : '-'), content.length); + return ( + '\n\n' + content + '\n' + underline + '\n\n' + ) + } else { + return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n' + } + } + }; + + rules.blockquote = { + filter: 'blockquote', + + replacement: function (content) { + content = content.replace(/^\n+|\n+$/g, ''); + content = content.replace(/^/gm, '> '); return '\n\n' + content + '\n\n' } - } -}; - -rules.listItem = { - filter: 'li', - - replacement: function (content, node, options) { - content = content - .replace(/^\n+/, '') // remove leading newlines - .replace(/\n+$/, '\n') // replace trailing newlines with just a single one - .replace(/\n/gm, '\n '); // indent - var prefix = options.bulletListMarker + ' '; - var parent = node.parentNode; - if (parent.nodeName === 'OL') { - var start = parent.getAttribute('start'); - var index = Array.prototype.indexOf.call(parent.children, node); - prefix = (start ? Number(start) + index : index + 1) + '. '; + }; + + rules.list = { + filter: ['ul', 'ol'], + + replacement: function (content, node) { + var parent = node.parentNode; + if (parent.nodeName === 'LI' && parent.lastElementChild === node) { + return '\n' + content + } else { + return '\n\n' + content + '\n\n' + } } - return ( - prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '') - ) - } -}; + }; -rules.indentedCodeBlock = { - filter: function (node, options) { - return ( - options.codeBlockStyle === 'indented' && - node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - ) - }, + rules.listItem = { + filter: 'li', + + replacement: function (content, node, options) { + content = content + .replace(/^\n+/, '') // remove leading newlines + .replace(/\n+$/, '\n') // replace trailing newlines with just a single one + .replace(/\n/gm, '\n '); // indent + var prefix = options.bulletListMarker + ' '; + var parent = node.parentNode; + if (parent.nodeName === 'OL') { + var start = parent.getAttribute('start'); + var index = Array.prototype.indexOf.call(parent.children, node); + prefix = (start ? Number(start) + index : index + 1) + '. '; + } + return ( + prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '') + ) + } + }; - replacement: function (content, node, options) { - return ( - '\n\n ' + - node.firstChild.textContent.replace(/\n/g, '\n ') + - '\n\n' - ) - } -}; + rules.indentedCodeBlock = { + filter: function (node, options) { + return ( + options.codeBlockStyle === 'indented' && + node.nodeName === 'PRE' && + node.firstChild && + node.firstChild.nodeName === 'CODE' + ) + }, -rules.fencedCodeBlock = { - filter: function (node, options) { - return ( - options.codeBlockStyle === 'fenced' && - node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - ) - }, + replacement: function (content, node, options) { + return ( + '\n\n ' + + node.firstChild.textContent.replace(/\n/g, '\n ') + + '\n\n' + ) + } + }; - replacement: function (content, node, options) { - var className = node.firstChild.className || ''; - var language = (className.match(/language-(\S+)/) || [null, ''])[1]; + rules.fencedCodeBlock = { + filter: function (node, options) { + return ( + options.codeBlockStyle === 'fenced' && + node.nodeName === 'PRE' && + node.firstChild && + node.firstChild.nodeName === 'CODE' + ) + }, - return ( - '\n\n' + options.fence + language + '\n' + - node.firstChild.textContent + - '\n' + options.fence + '\n\n' - ) - } -}; + replacement: function (content, node, options) { + var className = node.firstChild.getAttribute('class') || ''; + var language = (className.match(/language-(\S+)/) || [null, ''])[1]; + var code = node.firstChild.textContent; -rules.horizontalRule = { - filter: 'hr', + var fenceChar = options.fence.charAt(0); + var fenceSize = 3; + var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm'); - replacement: function (content, node, options) { - return '\n\n' + options.hr + '\n\n' - } -}; + var match; + while ((match = fenceInCodeRegex.exec(code))) { + if (match[0].length >= fenceSize) { + fenceSize = match[0].length + 1; + } + } -rules.inlineLink = { - filter: function (node, options) { - return ( - options.linkStyle === 'inlined' && - node.nodeName === 'A' && - node.getAttribute('href') - ) - }, + var fence = repeat(fenceChar, fenceSize); - replacement: function (content, node) { - var href = node.getAttribute('href'); - var title = node.title ? ' "' + node.title + '"' : ''; - return '[' + content + '](' + href + title + ')' - } -}; + return ( + '\n\n' + fence + language + '\n' + + code.replace(/\n$/, '') + + '\n' + fence + '\n\n' + ) + } + }; -rules.referenceLink = { - filter: function (node, options) { - return ( - options.linkStyle === 'referenced' && - node.nodeName === 'A' && - node.getAttribute('href') - ) - }, - - replacement: function (content, node, options) { - var href = node.getAttribute('href'); - var title = node.title ? ' "' + node.title + '"' : ''; - var replacement; - var reference; - - switch (options.linkReferenceStyle) { - case 'collapsed': - replacement = '[' + content + '][]'; - reference = '[' + content + ']: ' + href + title; - break - case 'shortcut': - replacement = '[' + content + ']'; - reference = '[' + content + ']: ' + href + title; - break - default: - var id = this.references.length + 1; - replacement = '[' + content + '][' + id + ']'; - reference = '[' + id + ']: ' + href + title; + rules.horizontalRule = { + filter: 'hr', + + replacement: function (content, node, options) { + return '\n\n' + options.hr + '\n\n' } + }; + + rules.inlineLink = { + filter: function (node, options) { + return ( + options.linkStyle === 'inlined' && + node.nodeName === 'A' && + node.getAttribute('href') + ) + }, + + replacement: function (content, node) { + var href = node.getAttribute('href'); + var title = cleanAttribute(node.getAttribute('title')); + if (title) title = ' "' + title + '"'; + return '[' + content + '](' + href + title + ')' + } + }; - this.references.push(reference); - return replacement - }, + rules.referenceLink = { + filter: function (node, options) { + return ( + options.linkStyle === 'referenced' && + node.nodeName === 'A' && + node.getAttribute('href') + ) + }, - references: [], + replacement: function (content, node, options) { + var href = node.getAttribute('href'); + var title = cleanAttribute(node.getAttribute('title')); + if (title) title = ' "' + title + '"'; + var replacement; + var reference; + + switch (options.linkReferenceStyle) { + case 'collapsed': + replacement = '[' + content + '][]'; + reference = '[' + content + ']: ' + href + title; + break + case 'shortcut': + replacement = '[' + content + ']'; + reference = '[' + content + ']: ' + href + title; + break + default: + var id = this.references.length + 1; + replacement = '[' + content + '][' + id + ']'; + reference = '[' + id + ']: ' + href + title; + } + + this.references.push(reference); + return replacement + }, - append: function (options) { - var references = ''; - if (this.references.length) { - references = '\n\n' + this.references.join('\n') + '\n\n'; - this.references = []; // Reset references + references: [], + + append: function (options) { + var references = ''; + if (this.references.length) { + references = '\n\n' + this.references.join('\n') + '\n\n'; + this.references = []; // Reset references + } + return references } - return references - } -}; + }; -rules.emphasis = { - filter: ['em', 'i'], + rules.emphasis = { + filter: ['em', 'i'], - replacement: function (content, node, options) { - if (!content.trim()) return '' - return options.emDelimiter + content + options.emDelimiter - } -}; + replacement: function (content, node, options) { + if (!content.trim()) return '' + return options.emDelimiter + content + options.emDelimiter + } + }; -rules.strong = { - filter: ['strong', 'b'], + rules.strong = { + filter: ['strong', 'b'], - replacement: function (content, node, options) { - if (!content.trim()) return '' - return options.strongDelimiter + content + options.strongDelimiter - } -}; + replacement: function (content, node, options) { + if (!content.trim()) return '' + return options.strongDelimiter + content + options.strongDelimiter + } + }; + + rules.code = { + filter: function (node) { + var hasSiblings = node.previousSibling || node.nextSibling; + var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings; -rules.code = { - filter: function (node) { - var hasSiblings = node.previousSibling || node.nextSibling; - var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings; + return node.nodeName === 'CODE' && !isCodeBlock + }, + + replacement: function (content) { + if (!content.trim()) return '' + + var delimiter = '`'; + var leadingSpace = ''; + var trailingSpace = ''; + var matches = content.match(/`+/gm); + if (matches) { + if (/^`/.test(content)) leadingSpace = ' '; + if (/`$/.test(content)) trailingSpace = ' '; + while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; + } - return node.nodeName === 'CODE' && !isCodeBlock - }, + return delimiter + leadingSpace + content + trailingSpace + delimiter + } + }; - replacement: function (content) { - if (!content.trim()) return '' + rules.image = { + filter: 'img', - var delimiter = '`'; - var leadingSpace = ''; - var trailingSpace = ''; - var matches = content.match(/`+/gm); - if (matches) { - if (/^`/.test(content)) leadingSpace = ' '; - if (/`$/.test(content)) trailingSpace = ' '; - while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; + replacement: function (content, node) { + var alt = cleanAttribute(node.getAttribute('alt')); + var src = node.getAttribute('src') || ''; + var title = cleanAttribute(node.getAttribute('title')); + var titlePart = title ? ' "' + title + '"' : ''; + return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' } + }; - return delimiter + leadingSpace + content + trailingSpace + delimiter + function cleanAttribute (attribute) { + return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '' } -}; -rules.image = { - filter: 'img', + /** + * Manages a collection of rules used to convert HTML to Markdown + */ + + function Rules (options) { + this.options = options; + this._keep = []; + this._remove = []; + + this.blankRule = { + replacement: options.blankReplacement + }; - replacement: function (content, node) { - var alt = node.alt || ''; - var src = node.getAttribute('src') || ''; - var title = node.title || ''; - var titlePart = title ? ' "' + title + '"' : ''; - return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' + this.keepReplacement = options.keepReplacement; + + this.defaultRule = { + replacement: options.defaultReplacement + }; + + this.array = []; + for (var key in options.rules) this.array.push(options.rules[key]); } -}; -/** - * Manages a collection of rules used to convert HTML to Markdown - */ + Rules.prototype = { + add: function (key, rule) { + this.array.unshift(rule); + }, -function Rules (options) { - this.options = options; - this._keep = []; - this._remove = []; + keep: function (filter) { + this._keep.unshift({ + filter: filter, + replacement: this.keepReplacement + }); + }, - this.blankRule = { - replacement: options.blankReplacement - }; + remove: function (filter) { + this._remove.unshift({ + filter: filter, + replacement: function () { + return '' + } + }); + }, - this.keepReplacement = options.keepReplacement; + forNode: function (node) { + if (node.isBlank) return this.blankRule + var rule; - this.defaultRule = { - replacement: options.defaultReplacement + if ((rule = findRule(this.array, node, this.options))) return rule + if ((rule = findRule(this._keep, node, this.options))) return rule + if ((rule = findRule(this._remove, node, this.options))) return rule + + return this.defaultRule + }, + + forEach: function (fn) { + for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); + } }; - this.array = []; - for (var key in options.rules) this.array.push(options.rules[key]); -} + function findRule (rules, node, options) { + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + if (filterValue(rule, node, options)) return rule + } + return void 0 + } -Rules.prototype = { - add: function (key, rule) { - this.array.unshift(rule); - }, + function filterValue (rule, node, options) { + var filter = rule.filter; + if (typeof filter === 'string') { + if (filter === node.nodeName.toLowerCase()) return true + } else if (Array.isArray(filter)) { + if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true + } else if (typeof filter === 'function') { + if (filter.call(rule, node, options)) return true + } else { + throw new TypeError('`filter` needs to be a string, array, or function') + } + } - keep: function (filter) { - this._keep.unshift({ - filter: filter, - replacement: this.keepReplacement - }); - }, + /** + * The collapseWhitespace function is adapted from collapse-whitespace + * by Luc Thevenard. + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Luc Thevenard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ - remove: function (filter) { - this._remove.unshift({ - filter: filter, - replacement: function () { - return '' - } - }); - }, - - forNode: function (node) { - if (node.isBlank) return this.blankRule - var rule; - - if ((rule = findRule(this.array, node, this.options))) return rule - if ((rule = findRule(this._keep, node, this.options))) return rule - if ((rule = findRule(this._remove, node, this.options))) return rule - - return this.defaultRule - }, - - forEach: function (fn) { - for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); - } -}; - -function findRule (rules, node, options) { - for (var i = 0; i < rules.length; i++) { - var rule = rules[i]; - if (filterValue(rule, node, options)) return rule - } - return void 0 -} - -function filterValue (rule, node, options) { - var filter = rule.filter; - if (typeof filter === 'string') { - if (filter === node.nodeName.toLowerCase()) return true - } else if (Array.isArray(filter)) { - if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true - } else if (typeof filter === 'function') { - if (filter.call(rule, node, options)) return true - } else { - throw new TypeError('`filter` needs to be a string, array, or function') - } -} - -/** - * The collapseWhitespace function is adapted from collapse-whitespace - * by Luc Thevenard. - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Luc Thevenard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * collapseWhitespace(options) removes extraneous whitespace from an the given element. - * - * @param {Object} options - */ -function collapseWhitespace (options) { - var element = options.element; - var isBlock = options.isBlock; - var isVoid = options.isVoid; - var isPre = options.isPre || function (node) { - return node.nodeName === 'PRE' - }; + /** + * collapseWhitespace(options) removes extraneous whitespace from an the given element. + * + * @param {Object} options + */ + function collapseWhitespace (options) { + var element = options.element; + var isBlock = options.isBlock; + var isVoid = options.isVoid; + var isPre = options.isPre || function (node) { + return node.nodeName === 'PRE' + }; - if (!element.firstChild || isPre(element)) return + if (!element.firstChild || isPre(element)) return - var prevText = null; - var prevVoid = false; + var prevText = null; + var prevVoid = false; - var prev = null; - var node = next(prev, element, isPre); + var prev = null; + var node = next(prev, element, isPre); - while (node !== element) { - if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE - var text = node.data.replace(/[ \r\n\t]+/g, ' '); + while (node !== element) { + if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE + var text = node.data.replace(/[ \r\n\t]+/g, ' '); - if ((!prevText || / $/.test(prevText.data)) && - !prevVoid && text[0] === ' ') { - text = text.substr(1); - } + if ((!prevText || / $/.test(prevText.data)) && + !prevVoid && text[0] === ' ') { + text = text.substr(1); + } - // `text` might be empty at this point. - if (!text) { + // `text` might be empty at this point. + if (!text) { + node = remove(node); + continue + } + + node.data = text; + + prevText = node; + } else if (node.nodeType === 1) { // Node.ELEMENT_NODE + if (isBlock(node) || node.nodeName === 'BR') { + if (prevText) { + prevText.data = prevText.data.replace(/ $/, ''); + } + + prevText = null; + prevVoid = false; + } else if (isVoid(node)) { + // Avoid trimming space around non-block, non-BR void elements. + prevText = null; + prevVoid = true; + } + } else { node = remove(node); continue } - node.data = text; - - prevText = node; - } else if (node.nodeType === 1) { // Node.ELEMENT_NODE - if (isBlock(node) || node.nodeName === 'BR') { - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - } + var nextNode = next(prev, node, isPre); + prev = node; + node = nextNode; + } - prevText = null; - prevVoid = false; - } else if (isVoid(node)) { - // Avoid trimming space around non-block, non-BR void elements. - prevText = null; - prevVoid = true; + if (prevText) { + prevText.data = prevText.data.replace(/ $/, ''); + if (!prevText.data) { + remove(prevText); } - } else { - node = remove(node); - continue } + } + + /** + * remove(node) removes the given node from the DOM and returns the + * next node in the sequence. + * + * @param {Node} node + * @return {Node} node + */ + function remove (node) { + var next = node.nextSibling || node.parentNode; + + node.parentNode.removeChild(node); - var nextNode = next(prev, node, isPre); - prev = node; - node = nextNode; + return next } - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - if (!prevText.data) { - remove(prevText); + /** + * next(prev, current, isPre) returns the next node in the sequence, given the + * current and previous nodes. + * + * @param {Node} prev + * @param {Node} current + * @param {Function} isPre + * @return {Node} + */ + function next (prev, current, isPre) { + if ((prev && prev.parentNode === current) || isPre(current)) { + return current.nextSibling || current.parentNode } + + return current.firstChild || current.nextSibling || current.parentNode } -} - -/** - * remove(node) removes the given node from the DOM and returns the - * next node in the sequence. - * - * @param {Node} node - * @return {Node} node - */ -function remove (node) { - var next = node.nextSibling || node.parentNode; - - node.parentNode.removeChild(node); - - return next -} - -/** - * next(prev, current, isPre) returns the next node in the sequence, given the - * current and previous nodes. - * - * @param {Node} prev - * @param {Node} current - * @param {Function} isPre - * @return {Node} - */ -function next (prev, current, isPre) { - if ((prev && prev.parentNode === current) || isPre(current)) { - return current.nextSibling || current.parentNode - } - - return current.firstChild || current.nextSibling || current.parentNode -} - -/* - * Set up window for Node.js - */ - -var root = (typeof window !== 'undefined' ? window : {}); - -/* - * Parsing HTML strings - */ - -function canParseHTMLNatively () { - var Parser = root.DOMParser; - var canParse = false; - - // Adapted from https://gist.github.com/1129031 - // Firefox/Opera/IE throw errors on unsupported types - try { - // WebKit returns null on unsupported types - if (new Parser().parseFromString('', 'text/html')) { - canParse = true; - } - } catch (e) {} - - return canParse -} - -function createHTMLParser () { - var Parser = function () {}; - - { - if (shouldUseActiveX()) { - Parser.prototype.parseFromString = function (string) { - var doc = new window.ActiveXObject('htmlfile'); - doc.designMode = 'on'; // disable on-page scripts - doc.open(); - doc.write(string); - doc.close(); - return doc - }; - } else { - Parser.prototype.parseFromString = function (string) { - var doc = document.implementation.createHTMLDocument(''); - doc.open(); - doc.write(string); - doc.close(); - return doc - }; + + /* + * Set up window for Node.js + */ + + var root = (typeof window !== 'undefined' ? window : {}); + + /* + * Parsing HTML strings + */ + + function canParseHTMLNatively () { + var Parser = root.DOMParser; + var canParse = false; + + // Adapted from https://gist.github.com/1129031 + // Firefox/Opera/IE throw errors on unsupported types + try { + // WebKit returns null on unsupported types + if (new Parser().parseFromString('', 'text/html')) { + canParse = true; + } + } catch (e) {} + + return canParse + } + + function createHTMLParser () { + var Parser = function () {}; + + { + if (shouldUseActiveX()) { + Parser.prototype.parseFromString = function (string) { + var doc = new window.ActiveXObject('htmlfile'); + doc.designMode = 'on'; // disable on-page scripts + doc.open(); + doc.write(string); + doc.close(); + return doc + }; + } else { + Parser.prototype.parseFromString = function (string) { + var doc = document.implementation.createHTMLDocument(''); + doc.open(); + doc.write(string); + doc.close(); + return doc + }; + } } + return Parser } - return Parser -} - -function shouldUseActiveX () { - var useActiveX = false; - try { - document.implementation.createHTMLDocument('').open(); - } catch (e) { - if (window.ActiveXObject) useActiveX = true; - } - return useActiveX -} - -var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser(); - -function RootNode (input) { - var root; - if (typeof input === 'string') { - var doc = htmlParser().parseFromString( - // DOM parsers arrange elements in the and . - // Wrapping in a custom element ensures elements are reliably arranged in - // a single element. - '' + input + '', - 'text/html' - ); - root = doc.getElementById('turndown-root'); - } else { - root = input.cloneNode(true); - } - collapseWhitespace({ - element: root, - isBlock: isBlock, - isVoid: isVoid - }); - - return root -} - -var _htmlParser; -function htmlParser () { - _htmlParser = _htmlParser || new HTMLParser(); - return _htmlParser -} - -function Node (node) { - node.isBlock = isBlock(node); - node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode; - node.isBlank = isBlank(node); - node.flankingWhitespace = flankingWhitespace(node); - return node -} - -function isBlank (node) { - return ( - ['A', 'TH', 'TD'].indexOf(node.nodeName) === -1 && - /^\s*$/i.test(node.textContent) && - !isVoid(node) && - !hasVoid(node) - ) -} - -function flankingWhitespace (node) { - var leading = ''; - var trailing = ''; - - if (!node.isBlock) { - var hasLeading = /^[ \r\n\t]/.test(node.textContent); - var hasTrailing = /[ \r\n\t]$/.test(node.textContent); - - if (hasLeading && !isFlankedByWhitespace('left', node)) { - leading = ' '; + + function shouldUseActiveX () { + var useActiveX = false; + try { + document.implementation.createHTMLDocument('').open(); + } catch (e) { + if (window.ActiveXObject) useActiveX = true; } - if (hasTrailing && !isFlankedByWhitespace('right', node)) { - trailing = ' '; + return useActiveX + } + + var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser(); + + function RootNode (input) { + var root; + if (typeof input === 'string') { + var doc = htmlParser().parseFromString( + // DOM parsers arrange elements in the and . + // Wrapping in a custom element ensures elements are reliably arranged in + // a single element. + '' + input + '', + 'text/html' + ); + root = doc.getElementById('turndown-root'); + } else { + root = input.cloneNode(true); } + collapseWhitespace({ + element: root, + isBlock: isBlock, + isVoid: isVoid + }); + + return root } - return { leading: leading, trailing: trailing } -} + var _htmlParser; + function htmlParser () { + _htmlParser = _htmlParser || new HTMLParser(); + return _htmlParser + } -function isFlankedByWhitespace (side, node) { - var sibling; - var regExp; - var isFlanked; + function Node (node) { + node.isBlock = isBlock(node); + node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode; + node.isBlank = isBlank(node); + node.flankingWhitespace = flankingWhitespace(node); + return node + } - if (side === 'left') { - sibling = node.previousSibling; - regExp = / $/; - } else { - sibling = node.nextSibling; - regExp = /^ /; + function isBlank (node) { + return ( + !isVoid(node) && + !isMeaningfulWhenBlank(node) && + /^\s*$/i.test(node.textContent) && + !hasVoid(node) && + !hasMeaningfulWhenBlank(node) + ) } - if (sibling) { - if (sibling.nodeType === 3) { - isFlanked = regExp.test(sibling.nodeValue); - } else if (sibling.nodeType === 1 && !isBlock(sibling)) { - isFlanked = regExp.test(sibling.textContent); + function flankingWhitespace (node) { + var leading = ''; + var trailing = ''; + + if (!node.isBlock) { + var hasLeading = /^\s/.test(node.textContent); + var hasTrailing = /\s$/.test(node.textContent); + var blankWithSpaces = node.isBlank && hasLeading && hasTrailing; + + if (hasLeading && !isFlankedByWhitespace('left', node)) { + leading = ' '; + } + + if (!blankWithSpaces && hasTrailing && !isFlankedByWhitespace('right', node)) { + trailing = ' '; + } } + + return { leading: leading, trailing: trailing } } - return isFlanked -} - -var reduce = Array.prototype.reduce; -var leadingNewLinesRegExp = /^\n*/; -var trailingNewLinesRegExp = /\n*$/; - -function TurndownService (options) { - if (!(this instanceof TurndownService)) return new TurndownService(options) - - var defaults = { - rules: rules, - headingStyle: 'setext', - hr: '* * *', - bulletListMarker: '*', - codeBlockStyle: 'indented', - fence: '```', - emDelimiter: '_', - strongDelimiter: '**', - linkStyle: 'inlined', - linkReferenceStyle: 'full', - br: ' ', - blankReplacement: function (content, node) { - return node.isBlock ? '\n\n' : '' + + function isFlankedByWhitespace (side, node) { + var sibling; + var regExp; + var isFlanked; + + if (side === 'left') { + sibling = node.previousSibling; + regExp = / $/; + } else { + sibling = node.nextSibling; + regExp = /^ /; + } + + if (sibling) { + if (sibling.nodeType === 3) { + isFlanked = regExp.test(sibling.nodeValue); + } else if (sibling.nodeType === 1 && !isBlock(sibling)) { + isFlanked = regExp.test(sibling.textContent); + } + } + return isFlanked + } + + var reduce = Array.prototype.reduce; + var leadingNewLinesRegExp = /^\n*/; + var trailingNewLinesRegExp = /\n*$/; + var escapes = [ + [/\\/g, '\\\\'], + [/\*/g, '\\*'], + [/^-/g, '\\-'], + [/^\+ /g, '\\+ '], + [/^(=+)/g, '\\$1'], + [/^(#{1,6}) /g, '\\$1 '], + [/`/g, '\\`'], + [/^~~~/g, '\\~~~'], + [/\[/g, '\\['], + [/\]/g, '\\]'], + [/^>/g, '\\>'], + [/_/g, '\\_'], + [/^(\d+)\. /g, '$1\\. '] + ]; + + function TurndownService (options) { + if (!(this instanceof TurndownService)) return new TurndownService(options) + + var defaults = { + rules: rules, + headingStyle: 'setext', + hr: '* * *', + bulletListMarker: '*', + codeBlockStyle: 'indented', + fence: '```', + emDelimiter: '_', + strongDelimiter: '**', + linkStyle: 'inlined', + linkReferenceStyle: 'full', + br: ' ', + blankReplacement: function (content, node) { + return node.isBlock ? '\n\n' : '' + }, + keepReplacement: function (content, node) { + return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML + }, + defaultReplacement: function (content, node) { + return node.isBlock ? '\n\n' + content + '\n\n' : content + } + }; + this.options = extend({}, defaults, options); + this.rules = new Rules(this.options); + } + + TurndownService.prototype = { + /** + * The entry point for converting a string or DOM node to Markdown + * @public + * @param {String|HTMLElement} input The string or DOM node to convert + * @returns A Markdown representation of the input + * @type String + */ + + turndown: function (input) { + if (!canConvert(input)) { + throw new TypeError( + input + ' is not a string, or an element/document/fragment node.' + ) + } + + if (input === '') return '' + + var output = process.call(this, new RootNode(input)); + return postProcess.call(this, output) + }, + + /** + * Add one or more plugins + * @public + * @param {Function|Array} plugin The plugin or array of plugins to add + * @returns The Turndown instance for chaining + * @type Object + */ + + use: function (plugin) { + if (Array.isArray(plugin)) { + for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); + } else if (typeof plugin === 'function') { + plugin(this); + } else { + throw new TypeError('plugin must be a Function or an Array of Functions') + } + return this + }, + + /** + * Adds a rule + * @public + * @param {String} key The unique key of the rule + * @param {Object} rule The rule + * @returns The Turndown instance for chaining + * @type Object + */ + + addRule: function (key, rule) { + this.rules.add(key, rule); + return this }, - keepReplacement: function (content, node) { - return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML + + /** + * Keep a node (as HTML) that matches the filter + * @public + * @param {String|Array|Function} filter The unique key of the rule + * @returns The Turndown instance for chaining + * @type Object + */ + + keep: function (filter) { + this.rules.keep(filter); + return this + }, + + /** + * Remove a node that matches the filter + * @public + * @param {String|Array|Function} filter The unique key of the rule + * @returns The Turndown instance for chaining + * @type Object + */ + + remove: function (filter) { + this.rules.remove(filter); + return this }, - defaultReplacement: function (content, node) { - return node.isBlock ? '\n\n' + content + '\n\n' : content + + /** + * Escapes Markdown syntax + * @public + * @param {String} string The string to escape + * @returns A string with Markdown syntax escaped + * @type String + */ + + escape: function (string) { + return escapes.reduce(function (accumulator, escape) { + return accumulator.replace(escape[0], escape[1]) + }, string) } }; - this.options = extend({}, defaults, options); - this.rules = new Rules(this.options); -} -TurndownService.prototype = { /** - * The entry point for converting a string or DOM node to Markdown - * @public - * @param {String|HTMLElement} input The string or DOM node to convert - * @returns A Markdown representation of the input + * Reduces a DOM node down to its Markdown string equivalent + * @private + * @param {HTMLElement} parentNode The node to convert + * @returns A Markdown representation of the node * @type String */ - turndown: function (input) { - if (!canConvert(input)) { - throw new TypeError( - input + ' is not a string, or an element/document/fragment node.' - ) - } + function process (parentNode) { + var self = this; + return reduce.call(parentNode.childNodes, function (output, node) { + node = new Node(node); - if (input === '') return '' + var replacement = ''; + if (node.nodeType === 3) { + replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); + } else if (node.nodeType === 1) { + replacement = replacementForNode.call(self, node); + } - var output = process.call(this, new RootNode(input)); - return postProcess.call(this, output) - }, + return join(output, replacement) + }, '') + } /** - * Add one or more plugins - * @public - * @param {Function|Array} plugin The plugin or array of plugins to add - * @returns The Turndown instance for chaining - * @type Object + * Appends strings as each rule requires and trims the output + * @private + * @param {String} output The conversion output + * @returns A trimmed version of the ouput + * @type String */ - use: function (plugin) { - if (Array.isArray(plugin)) { - for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); - } else if (typeof plugin === 'function') { - plugin(this); - } else { - throw new TypeError('plugin must be a Function or an Array of Functions') - } - return this - }, + function postProcess (output) { + var self = this; + this.rules.forEach(function (rule) { + if (typeof rule.append === 'function') { + output = join(output, rule.append(self.options)); + } + }); + + return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') + } /** - * Adds a rule - * @public - * @param {String} key The unique key of the rule - * @param {Object} rule The rule - * @returns The Turndown instance for chaining - * @type Object + * Converts an element node to its Markdown equivalent + * @private + * @param {HTMLElement} node The node to convert + * @returns A Markdown representation of the node + * @type String */ - addRule: function (key, rule) { - this.rules.add(key, rule); - return this - }, + function replacementForNode (node) { + var rule = this.rules.forNode(node); + var content = process.call(this, node); + var whitespace = node.flankingWhitespace; + if (whitespace.leading || whitespace.trailing) content = content.trim(); + return ( + whitespace.leading + + rule.replacement(content, node, this.options) + + whitespace.trailing + ) + } /** - * Keep a node (as HTML) that matches the filter - * @public - * @param {String|Array|Function} filter The unique key of the rule - * @returns The Turndown instance for chaining - * @type Object + * Determines the new lines between the current output and the replacement + * @private + * @param {String} output The current conversion output + * @param {String} replacement The string to append to the output + * @returns The whitespace to separate the current output and the replacement + * @type String */ - keep: function (filter) { - this.rules.keep(filter); - return this - }, + function separatingNewlines (output, replacement) { + var newlines = [ + output.match(trailingNewLinesRegExp)[0], + replacement.match(leadingNewLinesRegExp)[0] + ].sort(); + var maxNewlines = newlines[newlines.length - 1]; + return maxNewlines.length < 2 ? maxNewlines : '\n\n' + } - /** - * Remove a node that matches the filter - * @public - * @param {String|Array|Function} filter The unique key of the rule - * @returns The Turndown instance for chaining - * @type Object - */ + function join (string1, string2) { + var separator = separatingNewlines(string1, string2); + + // Remove trailing/leading newlines and replace with separator + string1 = string1.replace(trailingNewLinesRegExp, ''); + string2 = string2.replace(leadingNewLinesRegExp, ''); - remove: function (filter) { - this.rules.remove(filter); - return this - }, + return string1 + separator + string2 + } /** - * Escapes Markdown syntax - * @public - * @param {String} string The string to escape - * @returns A string with Markdown syntax escaped - * @type String + * Determines whether an input can be converted + * @private + * @param {String|HTMLElement} input Describe this parameter + * @returns Describe what it returns + * @type String|Object|Array|Boolean|Number */ - escape: function (string) { + function canConvert (input) { return ( - string - // Escape backslash escapes! - .replace(/\\(\S)/g, '\\\\$1') - - // Escape headings - .replace(/^(#{1,6} )/gm, '\\$1') - - // Escape hr - .replace(/^([-*_] *){3,}$/gm, function (match, character) { - return match.split(character).join('\\' + character) - }) - - // Escape ol bullet points - .replace(/^(\W* {0,3})(\d+)\. /gm, '$1$2\\. ') - - // Escape ul bullet points - .replace(/^([^\\\w]*)[*+-] /gm, function (match) { - return match.replace(/([*+-])/g, '\\$1') - }) - - // Escape blockquote indents - .replace(/^(\W* {0,3})> /gm, '$1\\> ') - - // Escape em/strong * - .replace(/\*+(?![*\s\W]).+?\*+/g, function (match) { - return match.replace(/\*/g, '\\*') - }) - - // Escape em/strong _ - .replace(/_+(?![_\s\W]).+?_+/g, function (match) { - return match.replace(/_/g, '\\_') - }) - - // Escape code _ - .replace(/`+(?![`\s\W]).+?`+/g, function (match) { - return match.replace(/`/g, '\\`') - }) - - // Escape link brackets - .replace(/[\[\]]/g, '\\$&') // eslint-disable-line no-useless-escape + input != null && ( + typeof input === 'string' || + (input.nodeType && ( + input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 + )) + ) ) } -}; - -/** - * Reduces a DOM node down to its Markdown string equivalent - * @private - * @param {HTMLElement} parentNode The node to convert - * @returns A Markdown representation of the node - * @type String - */ - -function process (parentNode) { - var self = this; - return reduce.call(parentNode.childNodes, function (output, node) { - node = new Node(node); - - var replacement = ''; - if (node.nodeType === 3) { - replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); - } else if (node.nodeType === 1) { - replacement = replacementForNode.call(self, node); - } - - return join(output, replacement) - }, '') -} - -/** - * Appends strings as each rule requires and trims the output - * @private - * @param {String} output The conversion output - * @returns A trimmed version of the ouput - * @type String - */ - -function postProcess (output) { - var self = this; - this.rules.forEach(function (rule) { - if (typeof rule.append === 'function') { - output = join(output, rule.append(self.options)); - } - }); - - return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') -} - -/** - * Converts an element node to its Markdown equivalent - * @private - * @param {HTMLElement} node The node to convert - * @returns A Markdown representation of the node - * @type String - */ - -function replacementForNode (node) { - var rule = this.rules.forNode(node); - var content = process.call(this, node); - var whitespace = node.flankingWhitespace; - if (whitespace.leading || whitespace.trailing) content = content.trim(); - return ( - whitespace.leading + - rule.replacement(content, node, this.options) + - whitespace.trailing - ) -} - -/** - * Determines the new lines between the current output and the replacement - * @private - * @param {String} output The current conversion output - * @param {String} replacement The string to append to the output - * @returns The whitespace to separate the current output and the replacement - * @type String - */ - -function separatingNewlines (output, replacement) { - var newlines = [ - output.match(trailingNewLinesRegExp)[0], - replacement.match(leadingNewLinesRegExp)[0] - ].sort(); - var maxNewlines = newlines[newlines.length - 1]; - return maxNewlines.length < 2 ? maxNewlines : '\n\n' -} - -function join (string1, string2) { - var separator = separatingNewlines(string1, string2); - - // Remove trailing/leading newlines and replace with separator - string1 = string1.replace(trailingNewLinesRegExp, ''); - string2 = string2.replace(leadingNewLinesRegExp, ''); - - return string1 + separator + string2 -} - -/** - * Determines whether an input can be converted - * @private - * @param {String|HTMLElement} input Describe this parameter - * @returns Describe what it returns - * @type String|Object|Array|Boolean|Number - */ - -function canConvert (input) { - return ( - input != null && ( - typeof input === 'string' || - (input.nodeType && ( - input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 - )) - ) - ) -} -return TurndownService; + return TurndownService; }()); diff --git a/js/turndown/turndown.min.js b/js/turndown/turndown.min.js index 8ea7878..2762d75 100644 --- a/js/turndown/turndown.min.js +++ b/js/turndown/turndown.min.js @@ -1 +1 @@ -var TurndownService=function(){"use strict";function extend(destination){for(var i=1;i-1)return!0}else{if("function"!=typeof filter)throw new TypeError("`filter` needs to be a string, array, or function");if(filter.call(rule,node,options))return!0}}function collapseWhitespace(options){var element=options.element,isBlock=options.isBlock,isVoid=options.isVoid,isPre=options.isPre||function(node){return"PRE"===node.nodeName};if(element.firstChild&&!isPre(element)){for(var prevText=null,prevVoid=!1,prev=null,node=next(prev,element,isPre);node!==element;){if(3===node.nodeType||4===node.nodeType){var text=node.data.replace(/[ \r\n\t]+/g," ");if(prevText&&!/ $/.test(prevText.data)||prevVoid||" "!==text[0]||(text=text.substr(1)),!text){node=remove(node);continue}node.data=text,prevText=node}else{if(1!==node.nodeType){node=remove(node);continue}isBlock(node)||"BR"===node.nodeName?(prevText&&(prevText.data=prevText.data.replace(/ $/,"")),prevText=null,prevVoid=!1):isVoid(node)&&(prevText=null,prevVoid=!0)}var nextNode=next(prev,node,isPre);prev=node,node=nextNode}prevText&&(prevText.data=prevText.data.replace(/ $/,""),prevText.data||remove(prevText))}}function remove(node){var next=node.nextSibling||node.parentNode;return node.parentNode.removeChild(node),next}function next(prev,current,isPre){return prev&&prev.parentNode===current||isPre(current)?current.nextSibling||current.parentNode:current.firstChild||current.nextSibling||current.parentNode}rules.paragraph={filter:"p",replacement:function(content){return"\n\n"+content+"\n\n"}},rules.lineBreak={filter:"br",replacement:function(content,node,options){return options.br+"\n"}},rules.heading={filter:["h1","h2","h3","h4","h5","h6"],replacement:function(content,node,options){var hLevel=Number(node.nodeName.charAt(1)),underline;return"setext"===options.headingStyle&&hLevel<3?"\n\n"+content+"\n"+repeat(1===hLevel?"=":"-",content.length)+"\n\n":"\n\n"+repeat("#",hLevel)+" "+content+"\n\n"}},rules.blockquote={filter:"blockquote",replacement:function(content){return"\n\n"+(content=(content=content.replace(/^\n+|\n+$/g,"")).replace(/^/gm,"> "))+"\n\n"}},rules.list={filter:["ul","ol"],replacement:function(content,node){var parent=node.parentNode;return"LI"===parent.nodeName&&parent.lastElementChild===node?"\n"+content:"\n\n"+content+"\n\n"}},rules.listItem={filter:"li",replacement:function(content,node,options){content=content.replace(/^\n+/,"").replace(/\n+$/,"\n").replace(/\n/gm,"\n ");var prefix=options.bulletListMarker+" ",parent=node.parentNode;if("OL"===parent.nodeName){var start=parent.getAttribute("start"),index=Array.prototype.indexOf.call(parent.children,node);prefix=(start?Number(start)+index:index+1)+". "}return prefix+content+(node.nextSibling&&!/\n$/.test(content)?"\n":"")}},rules.indentedCodeBlock={filter:function(node,options){return"indented"===options.codeBlockStyle&&"PRE"===node.nodeName&&node.firstChild&&"CODE"===node.firstChild.nodeName},replacement:function(content,node,options){return"\n\n "+node.firstChild.textContent.replace(/\n/g,"\n ")+"\n\n"}},rules.fencedCodeBlock={filter:function(node,options){return"fenced"===options.codeBlockStyle&&"PRE"===node.nodeName&&node.firstChild&&"CODE"===node.firstChild.nodeName},replacement:function(content,node,options){var className,language=((node.firstChild.className||"").match(/language-(\S+)/)||[null,""])[1];return"\n\n"+options.fence+language+"\n"+node.firstChild.textContent+"\n"+options.fence+"\n\n"}},rules.horizontalRule={filter:"hr",replacement:function(content,node,options){return"\n\n"+options.hr+"\n\n"}},rules.inlineLink={filter:function(node,options){return"inlined"===options.linkStyle&&"A"===node.nodeName&&node.getAttribute("href")},replacement:function(content,node){var href,title;return"["+content+"]("+node.getAttribute("href")+(node.title?' "'+node.title+'"':"")+")"}},rules.referenceLink={filter:function(node,options){return"referenced"===options.linkStyle&&"A"===node.nodeName&&node.getAttribute("href")},replacement:function(content,node,options){var href=node.getAttribute("href"),title=node.title?' "'+node.title+'"':"",replacement,reference;switch(options.linkReferenceStyle){case"collapsed":replacement="["+content+"][]",reference="["+content+"]: "+href+title;break;case"shortcut":replacement="["+content+"]",reference="["+content+"]: "+href+title;break;default:var id=this.references.length+1;replacement="["+content+"]["+id+"]",reference="["+id+"]: "+href+title}return this.references.push(reference),replacement},references:[],append:function(options){var references="";return this.references.length&&(references="\n\n"+this.references.join("\n")+"\n\n",this.references=[]),references}},rules.emphasis={filter:["em","i"],replacement:function(content,node,options){return content.trim()?options.emDelimiter+content+options.emDelimiter:""}},rules.strong={filter:["strong","b"],replacement:function(content,node,options){return content.trim()?options.strongDelimiter+content+options.strongDelimiter:""}},rules.code={filter:function(node){var hasSiblings=node.previousSibling||node.nextSibling,isCodeBlock="PRE"===node.parentNode.nodeName&&!hasSiblings;return"CODE"===node.nodeName&&!isCodeBlock},replacement:function(content){if(!content.trim())return"";var delimiter="`",leadingSpace="",trailingSpace="",matches=content.match(/`+/gm);if(matches)for(/^`/.test(content)&&(leadingSpace=" "),/`$/.test(content)&&(trailingSpace=" ");-1!==matches.indexOf(delimiter);)delimiter+="`";return delimiter+leadingSpace+content+trailingSpace+delimiter}},rules.image={filter:"img",replacement:function(content,node){var alt=node.alt||"",src=node.getAttribute("src")||"",title=node.title||"",titlePart;return src?"!["+alt+"]("+src+(title?' "'+title+'"':"")+")":""}},Rules.prototype={add:function(key,rule){this.array.unshift(rule)},keep:function(filter){this._keep.unshift({filter:filter,replacement:this.keepReplacement})},remove:function(filter){this._remove.unshift({filter:filter,replacement:function(){return""}})},forNode:function(node){return node.isBlank?this.blankRule:(rule=findRule(this.array,node,this.options))?rule:(rule=findRule(this._keep,node,this.options))?rule:(rule=findRule(this._remove,node,this.options))?rule:this.defaultRule;var rule},forEach:function(fn){for(var i=0;i'+input+"","text/html").getElementById("turndown-root"):root=input.cloneNode(!0);return collapseWhitespace({element:root,isBlock:isBlock,isVoid:isVoid}),root}function htmlParser(){return _htmlParser=_htmlParser||new HTMLParser}function Node(node){return node.isBlock=isBlock(node),node.isCode="code"===node.nodeName.toLowerCase()||node.parentNode.isCode,node.isBlank=isBlank(node),node.flankingWhitespace=flankingWhitespace(node),node}function isBlank(node){return-1===["A","TH","TD"].indexOf(node.nodeName)&&/^\s*$/i.test(node.textContent)&&!isVoid(node)&&!hasVoid(node)}function flankingWhitespace(node){var leading="",trailing="";if(!node.isBlock){var hasLeading=/^[ \r\n\t]/.test(node.textContent),hasTrailing=/[ \r\n\t]$/.test(node.textContent);hasLeading&&!isFlankedByWhitespace("left",node)&&(leading=" "),hasTrailing&&!isFlankedByWhitespace("right",node)&&(trailing=" ")}return{leading:leading,trailing:trailing}}function isFlankedByWhitespace(side,node){var sibling,regExp,isFlanked;return"left"===side?(sibling=node.previousSibling,regExp=/ $/):(sibling=node.nextSibling,regExp=/^ /),sibling&&(3===sibling.nodeType?isFlanked=regExp.test(sibling.nodeValue):1!==sibling.nodeType||isBlock(sibling)||(isFlanked=regExp.test(sibling.textContent))),isFlanked}var reduce=Array.prototype.reduce,leadingNewLinesRegExp=/^\n*/,trailingNewLinesRegExp=/\n*$/;function TurndownService(options){if(!(this instanceof TurndownService))return new TurndownService(options);var defaults={rules:rules,headingStyle:"setext",hr:"* * *",bulletListMarker:"*",codeBlockStyle:"indented",fence:"```",emDelimiter:"_",strongDelimiter:"**",linkStyle:"inlined",linkReferenceStyle:"full",br:" ",blankReplacement:function(content,node){return node.isBlock?"\n\n":""},keepReplacement:function(content,node){return node.isBlock?"\n\n"+node.outerHTML+"\n\n":node.outerHTML},defaultReplacement:function(content,node){return node.isBlock?"\n\n"+content+"\n\n":content}};this.options=extend({},defaults,options),this.rules=new Rules(this.options)}function process(parentNode){var self=this;return reduce.call(parentNode.childNodes,(function(output,node){var replacement="";return 3===(node=new Node(node)).nodeType?replacement=node.isCode?node.nodeValue:self.escape(node.nodeValue):1===node.nodeType&&(replacement=replacementForNode.call(self,node)),join(output,replacement)}),"")}function postProcess(output){var self=this;return this.rules.forEach((function(rule){"function"==typeof rule.append&&(output=join(output,rule.append(self.options)))})),output.replace(/^[\t\r\n]+/,"").replace(/[\t\r\n\s]+$/,"")}function replacementForNode(node){var rule=this.rules.forNode(node),content=process.call(this,node),whitespace=node.flankingWhitespace;return(whitespace.leading||whitespace.trailing)&&(content=content.trim()),whitespace.leading+rule.replacement(content,node,this.options)+whitespace.trailing}function separatingNewlines(output,replacement){var newlines=[output.match(trailingNewLinesRegExp)[0],replacement.match(leadingNewLinesRegExp)[0]].sort(),maxNewlines=newlines[newlines.length-1];return maxNewlines.length<2?maxNewlines:"\n\n"}function join(string1,string2){var separator=separatingNewlines(string1,string2);return(string1=string1.replace(trailingNewLinesRegExp,""))+separator+(string2=string2.replace(leadingNewLinesRegExp,""))}function canConvert(input){return null!=input&&("string"==typeof input||input.nodeType&&(1===input.nodeType||9===input.nodeType||11===input.nodeType))}return TurndownService.prototype={turndown:function(input){if(!canConvert(input))throw new TypeError(input+" is not a string, or an element/document/fragment node.");if(""===input)return"";var output=process.call(this,new RootNode(input));return postProcess.call(this,output)},use:function(plugin){if(Array.isArray(plugin))for(var i=0;i /gm,"$1\\> ").replace(/\*+(?![*\s\W]).+?\*+/g,(function(match){return match.replace(/\*/g,"\\*")})).replace(/_+(?![_\s\W]).+?_+/g,(function(match){return match.replace(/_/g,"\\_")})).replace(/`+(?![`\s\W]).+?`+/g,(function(match){return match.replace(/`/g,"\\`")})).replace(/[\[\]]/g,"\\$&")}},TurndownService}(); \ No newline at end of file +var TurndownService=function(){"use strict";function extend(destination){for(var i=1;i=0}function has(node,tagNames){return node.getElementsByTagName&&tagNames.some((function(tagName){return node.getElementsByTagName(tagName).length}))}var rules={};function cleanAttribute(attribute){return attribute?attribute.replace(/(\n+\s*)+/g,"\n"):""}function Rules(options){for(var key in this.options=options,this._keep=[],this._remove=[],this.blankRule={replacement:options.blankReplacement},this.keepReplacement=options.keepReplacement,this.defaultRule={replacement:options.defaultReplacement},this.array=[],options.rules)this.array.push(options.rules[key])}function findRule(rules,node,options){for(var i=0;i-1)return!0}else{if("function"!=typeof filter)throw new TypeError("`filter` needs to be a string, array, or function");if(filter.call(rule,node,options))return!0}}function collapseWhitespace(options){var element=options.element,isBlock=options.isBlock,isVoid=options.isVoid,isPre=options.isPre||function(node){return"PRE"===node.nodeName};if(element.firstChild&&!isPre(element)){for(var prevText=null,prevVoid=!1,prev=null,node=next(prev,element,isPre);node!==element;){if(3===node.nodeType||4===node.nodeType){var text=node.data.replace(/[ \r\n\t]+/g," ");if(prevText&&!/ $/.test(prevText.data)||prevVoid||" "!==text[0]||(text=text.substr(1)),!text){node=remove(node);continue}node.data=text,prevText=node}else{if(1!==node.nodeType){node=remove(node);continue}isBlock(node)||"BR"===node.nodeName?(prevText&&(prevText.data=prevText.data.replace(/ $/,"")),prevText=null,prevVoid=!1):isVoid(node)&&(prevText=null,prevVoid=!0)}var nextNode=next(prev,node,isPre);prev=node,node=nextNode}prevText&&(prevText.data=prevText.data.replace(/ $/,""),prevText.data||remove(prevText))}}function remove(node){var next=node.nextSibling||node.parentNode;return node.parentNode.removeChild(node),next}function next(prev,current,isPre){return prev&&prev.parentNode===current||isPre(current)?current.nextSibling||current.parentNode:current.firstChild||current.nextSibling||current.parentNode}rules.paragraph={filter:"p",replacement:function(content){return"\n\n"+content+"\n\n"}},rules.lineBreak={filter:"br",replacement:function(content,node,options){return options.br+"\n"}},rules.heading={filter:["h1","h2","h3","h4","h5","h6"],replacement:function(content,node,options){var hLevel=Number(node.nodeName.charAt(1)),underline;return"setext"===options.headingStyle&&hLevel<3?"\n\n"+content+"\n"+repeat(1===hLevel?"=":"-",content.length)+"\n\n":"\n\n"+repeat("#",hLevel)+" "+content+"\n\n"}},rules.blockquote={filter:"blockquote",replacement:function(content){return"\n\n"+(content=(content=content.replace(/^\n+|\n+$/g,"")).replace(/^/gm,"> "))+"\n\n"}},rules.list={filter:["ul","ol"],replacement:function(content,node){var parent=node.parentNode;return"LI"===parent.nodeName&&parent.lastElementChild===node?"\n"+content:"\n\n"+content+"\n\n"}},rules.listItem={filter:"li",replacement:function(content,node,options){content=content.replace(/^\n+/,"").replace(/\n+$/,"\n").replace(/\n/gm,"\n ");var prefix=options.bulletListMarker+" ",parent=node.parentNode;if("OL"===parent.nodeName){var start=parent.getAttribute("start"),index=Array.prototype.indexOf.call(parent.children,node);prefix=(start?Number(start)+index:index+1)+". "}return prefix+content+(node.nextSibling&&!/\n$/.test(content)?"\n":"")}},rules.indentedCodeBlock={filter:function(node,options){return"indented"===options.codeBlockStyle&&"PRE"===node.nodeName&&node.firstChild&&"CODE"===node.firstChild.nodeName},replacement:function(content,node,options){return"\n\n "+node.firstChild.textContent.replace(/\n/g,"\n ")+"\n\n"}},rules.fencedCodeBlock={filter:function(node,options){return"fenced"===options.codeBlockStyle&&"PRE"===node.nodeName&&node.firstChild&&"CODE"===node.firstChild.nodeName},replacement:function(content,node,options){for(var className,language=((node.firstChild.getAttribute("class")||"").match(/language-(\S+)/)||[null,""])[1],code=node.firstChild.textContent,fenceChar=options.fence.charAt(0),fenceSize=3,fenceInCodeRegex=new RegExp("^"+fenceChar+"{3,}","gm"),match;match=fenceInCodeRegex.exec(code);)match[0].length>=fenceSize&&(fenceSize=match[0].length+1);var fence=repeat(fenceChar,fenceSize);return"\n\n"+fence+language+"\n"+code.replace(/\n$/,"")+"\n"+fence+"\n\n"}},rules.horizontalRule={filter:"hr",replacement:function(content,node,options){return"\n\n"+options.hr+"\n\n"}},rules.inlineLink={filter:function(node,options){return"inlined"===options.linkStyle&&"A"===node.nodeName&&node.getAttribute("href")},replacement:function(content,node){var href=node.getAttribute("href"),title=cleanAttribute(node.getAttribute("title"));return title&&(title=' "'+title+'"'),"["+content+"]("+href+title+")"}},rules.referenceLink={filter:function(node,options){return"referenced"===options.linkStyle&&"A"===node.nodeName&&node.getAttribute("href")},replacement:function(content,node,options){var href=node.getAttribute("href"),title=cleanAttribute(node.getAttribute("title")),replacement,reference;switch(title&&(title=' "'+title+'"'),options.linkReferenceStyle){case"collapsed":replacement="["+content+"][]",reference="["+content+"]: "+href+title;break;case"shortcut":replacement="["+content+"]",reference="["+content+"]: "+href+title;break;default:var id=this.references.length+1;replacement="["+content+"]["+id+"]",reference="["+id+"]: "+href+title}return this.references.push(reference),replacement},references:[],append:function(options){var references="";return this.references.length&&(references="\n\n"+this.references.join("\n")+"\n\n",this.references=[]),references}},rules.emphasis={filter:["em","i"],replacement:function(content,node,options){return content.trim()?options.emDelimiter+content+options.emDelimiter:""}},rules.strong={filter:["strong","b"],replacement:function(content,node,options){return content.trim()?options.strongDelimiter+content+options.strongDelimiter:""}},rules.code={filter:function(node){var hasSiblings=node.previousSibling||node.nextSibling,isCodeBlock="PRE"===node.parentNode.nodeName&&!hasSiblings;return"CODE"===node.nodeName&&!isCodeBlock},replacement:function(content){if(!content.trim())return"";var delimiter="`",leadingSpace="",trailingSpace="",matches=content.match(/`+/gm);if(matches)for(/^`/.test(content)&&(leadingSpace=" "),/`$/.test(content)&&(trailingSpace=" ");-1!==matches.indexOf(delimiter);)delimiter+="`";return delimiter+leadingSpace+content+trailingSpace+delimiter}},rules.image={filter:"img",replacement:function(content,node){var alt=cleanAttribute(node.getAttribute("alt")),src=node.getAttribute("src")||"",title=cleanAttribute(node.getAttribute("title")),titlePart;return src?"!["+alt+"]("+src+(title?' "'+title+'"':"")+")":""}},Rules.prototype={add:function(key,rule){this.array.unshift(rule)},keep:function(filter){this._keep.unshift({filter:filter,replacement:this.keepReplacement})},remove:function(filter){this._remove.unshift({filter:filter,replacement:function(){return""}})},forNode:function(node){return node.isBlank?this.blankRule:(rule=findRule(this.array,node,this.options))?rule:(rule=findRule(this._keep,node,this.options))?rule:(rule=findRule(this._remove,node,this.options))?rule:this.defaultRule;var rule},forEach:function(fn){for(var i=0;i'+input+"","text/html").getElementById("turndown-root"):root=input.cloneNode(!0);return collapseWhitespace({element:root,isBlock:isBlock,isVoid:isVoid}),root}function htmlParser(){return _htmlParser=_htmlParser||new HTMLParser}function Node(node){return node.isBlock=isBlock(node),node.isCode="code"===node.nodeName.toLowerCase()||node.parentNode.isCode,node.isBlank=isBlank(node),node.flankingWhitespace=flankingWhitespace(node),node}function isBlank(node){return!isVoid(node)&&!isMeaningfulWhenBlank(node)&&/^\s*$/i.test(node.textContent)&&!hasVoid(node)&&!hasMeaningfulWhenBlank(node)}function flankingWhitespace(node){var leading="",trailing="";if(!node.isBlock){var hasLeading=/^\s/.test(node.textContent),hasTrailing=/\s$/.test(node.textContent),blankWithSpaces=node.isBlank&&hasLeading&&hasTrailing;hasLeading&&!isFlankedByWhitespace("left",node)&&(leading=" "),blankWithSpaces||!hasTrailing||isFlankedByWhitespace("right",node)||(trailing=" ")}return{leading:leading,trailing:trailing}}function isFlankedByWhitespace(side,node){var sibling,regExp,isFlanked;return"left"===side?(sibling=node.previousSibling,regExp=/ $/):(sibling=node.nextSibling,regExp=/^ /),sibling&&(3===sibling.nodeType?isFlanked=regExp.test(sibling.nodeValue):1!==sibling.nodeType||isBlock(sibling)||(isFlanked=regExp.test(sibling.textContent))),isFlanked}var reduce=Array.prototype.reduce,leadingNewLinesRegExp=/^\n*/,trailingNewLinesRegExp=/\n*$/,escapes=[[/\\/g,"\\\\"],[/\*/g,"\\*"],[/^-/g,"\\-"],[/^\+ /g,"\\+ "],[/^(=+)/g,"\\$1"],[/^(#{1,6}) /g,"\\$1 "],[/`/g,"\\`"],[/^~~~/g,"\\~~~"],[/\[/g,"\\["],[/\]/g,"\\]"],[/^>/g,"\\>"],[/_/g,"\\_"],[/^(\d+)\. /g,"$1\\. "]];function TurndownService(options){if(!(this instanceof TurndownService))return new TurndownService(options);var defaults={rules:rules,headingStyle:"setext",hr:"* * *",bulletListMarker:"*",codeBlockStyle:"indented",fence:"```",emDelimiter:"_",strongDelimiter:"**",linkStyle:"inlined",linkReferenceStyle:"full",br:" ",blankReplacement:function(content,node){return node.isBlock?"\n\n":""},keepReplacement:function(content,node){return node.isBlock?"\n\n"+node.outerHTML+"\n\n":node.outerHTML},defaultReplacement:function(content,node){return node.isBlock?"\n\n"+content+"\n\n":content}};this.options=extend({},defaults,options),this.rules=new Rules(this.options)}function process(parentNode){var self=this;return reduce.call(parentNode.childNodes,(function(output,node){var replacement="";return 3===(node=new Node(node)).nodeType?replacement=node.isCode?node.nodeValue:self.escape(node.nodeValue):1===node.nodeType&&(replacement=replacementForNode.call(self,node)),join(output,replacement)}),"")}function postProcess(output){var self=this;return this.rules.forEach((function(rule){"function"==typeof rule.append&&(output=join(output,rule.append(self.options)))})),output.replace(/^[\t\r\n]+/,"").replace(/[\t\r\n\s]+$/,"")}function replacementForNode(node){var rule=this.rules.forNode(node),content=process.call(this,node),whitespace=node.flankingWhitespace;return(whitespace.leading||whitespace.trailing)&&(content=content.trim()),whitespace.leading+rule.replacement(content,node,this.options)+whitespace.trailing}function separatingNewlines(output,replacement){var newlines=[output.match(trailingNewLinesRegExp)[0],replacement.match(leadingNewLinesRegExp)[0]].sort(),maxNewlines=newlines[newlines.length-1];return maxNewlines.length<2?maxNewlines:"\n\n"}function join(string1,string2){var separator=separatingNewlines(string1,string2);return(string1=string1.replace(trailingNewLinesRegExp,""))+separator+(string2=string2.replace(leadingNewLinesRegExp,""))}function canConvert(input){return null!=input&&("string"==typeof input||input.nodeType&&(1===input.nodeType||9===input.nodeType||11===input.nodeType))}return TurndownService.prototype={turndown:function(input){if(!canConvert(input))throw new TypeError(input+" is not a string, or an element/document/fragment node.");if(""===input)return"";var output=process.call(this,new RootNode(input));return postProcess.call(this,output)},use:function(plugin){if(Array.isArray(plugin))for(var i=0;iconfig->get('notes_basepath', false).$rcmail->user->get_username().$rcmail->config->get('notes_folder', false); - if(file_exists($notes_path.$old_name && $old_name != '')) { + + if(file_exists($notes_path.$old_name) && $old_name != '') { if($old_name != $new_name) if(!rename($notes_path.$old_name, $notes_path.$new_name)) die('Could not rename file.'); } elseif ($old_name != "") { error_log('PrimitiveNotes: Note not found, can\`t save note.'); @@ -401,6 +402,7 @@ function human_filesize($bytes, $decimals = 2) { <?PHP echo $note['name'] ?> + @@ -420,7 +422,7 @@ function human_filesize($bytes, $decimals = 2) { - +