// ICE coder by Matt Pass // Free to use it for your own purposes, commercial or not, just let me know of any cool uses or customisations. :) // No warranty or liability accepted for anything, all responsibility of use is your own. // Latest version: https://github.com/mattpass/ICEcoder // Twitter: @mattpass var ICEcoder = { // Define settings filesW: 250, // Initial width of the files pane minFilesW: 15, // Min width of the files pane maxFilesW: 250, // Max width of the files pane selectedTab: 0, // The tab that's currently selected changedContent: [], // Binary array to indicate which tabs have changed ctrlKeyDown: false, // Indicates if CTRL keydown shiftKeyDown: false, // Indicates if Shift keydown delKeyDown: false, // Indicates if DEL keydown canSwitchTabs: true, // Stops switching of tabs when trying to close openFiles: [], // Array of open file URLs cMInstances: [], // List of CodeMirror instance no's nextcMInstance: 1, // Next available CodeMirror instance no selectedFiles: [], // Array of selected files findMode: false, // States if we're in find/replace mode lockedNav: true, // Nav is locked or not codeAssist: true, // Assist user with their coding mouseDown: false, // If the mouse is down or not draggingFilesW: false, // If we're dragging the file manager width or not serverQueueItems: [], // Array of URLs to call in order // Don't consider these tags as part of nesting as they're singles, JS, PHP or Ruby code blocks tagNestExceptions: ["!DOCTYPE","meta","link","img","br","hr","input","script","?php","?","%"], // On load, set aliases, set the layout and get the nest location init: function() { var aliasArray = ["header","files","account","fmLock","filesFrame","editor","tabsBar","findBar","content","footer","nestValid","nestDisplay","charDisplay"]; // Create our ID aliases for (var i=0;i value fileName = ICEcoder.openFiles[ICEcoder.selectedTab-1]; if (fileName.indexOf(".js")<0&&fileName.indexOf(".rb")&&fileName.indexOf(".css")<0) { cM = ICEcoder.getcMInstance(); content = cM.getValue(); if (top.ICEcoder.codeAssist) {content = content.replace(/ & /g,' & ');}; content = content.replace(//g,''); // Then set the content in the editor & clear the history cM.setValue(content); cM.clearHistory(); } }, // Work out the nesting depth location on demand and update our display if required getNestLocation: function(updateNestDisplay) { var cM, openTag, nestCheck, startPos, tagStart, canDoTheEndTag, tagEnd, tagEndJS, fileName; cM = ICEcoder.getcMInstance(); nestCheck = cM.getValue(); // Set up array to store nest data, a var to establish if a tag is open and another to establish if we're in a code block ICEcoder.htmlTagArray = [], openTag = false, ICEcoder.codeBlock = false; // For every character from the start to our caret position for(var i=0;i<=ICEcoder.caretPos;i++) { // If we find a < tag and we're not within a tag, change the open tag state & set our start position if(nestCheck.charAt(i)=="<" && openTag==false) { openTag=true; startPos=i+1; // Get the tag name and if it's the start of a code block, set the var for that tagStart=nestCheck.substr(startPos,nestCheck.length).split(" ")[0].split(">")[0].split("\n")[0]; if (tagStart=="script"||tagStart=="?php"||tagStart=="?"||tagStart=="%") {ICEcoder.codeBlock=true} if (tagStart!="") {ICEcoder.tagStart = tagStart} }; // If we find a > tag and we're within a tag or codeblock if(nestCheck.charAt(i)==">" && (openTag||ICEcoder.codeBlock)) { // Get the tag name tagString=nestCheck.substr(0,i); tagString=tagString.substr(tagString.lastIndexOf('<')+1,tagString.length); tagString=tagString.split(" ")[0]; ICEcoder.tagString = tagString; canDoTheEndTag=true; // Check it's not on our list of exceptions for (var j=0;j'+ICEcoder.htmlTagArray[i]+''; if(i ICEcoder.minFilesW+1 ? ICEcoder.filesW -= Math.ceil((ICEcoder.filesW-ICEcoder.minFilesW)/2) : ICEcoder.filesW = ICEcoder.minFilesW; } if ((expandContract=="expand" && ICEcoder.filesW == ICEcoder.maxFilesW)||(expandContract=="contract" && ICEcoder.filesW == ICEcoder.minFilesW)) { clearInterval(ICEcoder.changeFilesInt); } // Redo the layout to match ICEcoder.setLayout(); }, // Can click-drag file manager width? canResizeFilesW: function() { // If we have the cursor set we must be able! if (top.document.body.style.cursor == "w-resize") { // If our mouse is down and we're within a 250-400px range if (top.ICEcoder.mouseDown) { if (top.ICEcoder.mouseX >=250 && top.ICEcoder.mouseX <= 400) { top.ICEcoder.filesW = top.ICEcoder.maxFilesW = top.ICEcoder.mouseX; } else if (top.ICEcoder.mouseX <250) { top.ICEcoder.filesW = top.ICEcoder.maxFilesW = 250; } else { top.ICEcoder.filesW = top.ICEcoder.maxFilesW = 400; } // Set various widths based on the new width top.ICEcoder.files.style.width = top.ICEcoder.account.style.width = top.ICEcoder.filesFrame.style.width = top.ICEcoder.filesW + "px"; top.ICEcoder.setLayout(); top.ICEcoder.draggingFilesW = true; } } else { top.ICEcoder.draggingFilesW = false; } }, // Change tabs by switching visibility of instances switchTab: function(newTab) { var cM; // Identify tab that's currently selected & get the instance ICEcoder.selectedTab = newTab; cM = ICEcoder.getcMInstance(); // Switch mode to HTML, PHP, CSS etc ICEcoder.switchMode(); // Set all cM instances to be hidden, then make our selected instance visable for (var i=0;i'; top.document.getElementById('tab'+(top.ICEcoder.openFiles.length)).style.display = "inline-block"; top.document.getElementById('tab'+(top.ICEcoder.openFiles.length)).innerHTML = top.ICEcoder.openFiles[top.ICEcoder.openFiles.length-1] + " " + closeTabLink; // Highlight it and state it's selected top.ICEcoder.redoTabHighlight(top.ICEcoder.openFiles.length); top.ICEcoder.selectedTab=top.ICEcoder.openFiles.length; // Add a new value ready to indicate if this content has been changed top.ICEcoder.changedContent.push(0); top.ICEcoder.setLastOpenedFiles(); }, // Create a new tab for a file renameTab: function(tabNum,newName) { var closeTabLink; // Push new file into array top.ICEcoder.openFiles[tabNum] = newName; // Setup a new tab closeTabLink = ''; top.document.getElementById('tab'+tabNum).innerHTML = top.ICEcoder.openFiles[tabNum] + " " + closeTabLink; }, // Indicate if the nesting structure of the code is OK updateNestingIndicator: function () { var cM, fileName; cM = ICEcoder.getcMInstance(); fileName = ICEcoder.openFiles[ICEcoder.selectedTab-1]; ICEcoder.caretPos=cM.getValue().length; ICEcoder.getNestLocation(); // Nesting is OK if at the end of the file we have no nests left, or it's a JS, Ruby or CSS file if (ICEcoder.htmlTagArray.length==0||fileName.indexOf(".js")>0||fileName.indexOf(".rb")>0||fileName.indexOf(".css")>0) { ICEcoder.nestValid.style.backgroundColor="#00bb00"; ICEcoder.nestValid.innerHTML = "Nesting OK"; } else { ICEcoder.nestValid.style.backgroundColor="#ff0000"; ICEcoder.nestValid.innerHTML = "Nesting Broken"; } }, // Get the caret position on demand getCaretPosition: function() { var cM, content, line, char, charPos, charCount; cM = ICEcoder.getcMInstance(); content = cM.getValue(); line = cM.getCursor().line; char = cM.getCursor().ch; charPos = 0; for (var i=0;icaretChunk.lastIndexOf("")&&caretLocType=="Unknown") {caretLocType = "JavaScript"}; if (caretChunk.lastIndexOf("caretChunk.lastIndexOf("?>")&&caretLocType=="Unknown") {caretLocType = "PHP"}; if (caretChunk.lastIndexOf("<%")>caretChunk.lastIndexOf("%>")&&caretLocType=="Unknown") {caretLocType = "Ruby"}; if (caretChunk.lastIndexOf("<")>caretChunk.lastIndexOf(">")&&caretLocType=="Unknown") {caretLocType = "HTML"}; if (caretLocType=="Unknown") {caretLocType = "Content"}; fileName = ICEcoder.openFiles[ICEcoder.selectedTab-1]; if (fileName.indexOf(".js")>0) {caretLocType="JavaScript"}; if (fileName.indexOf(".rb")>0) {caretLocType="Ruby"}; if (fileName.indexOf(".css")>0) {caretLocType="CSS"}; ICEcoder.caretLocType = caretLocType; // If we're in a JS, PHP or Ruby code block, add that to the nest display if (caretLocType=="JavaScript"||caretLocType=="PHP"||caretLocType=="Ruby") { ICEcoder.nestDisplay.innerHTML += " > " + caretLocType; } }, // Alter array indicating which files have changed redoChangedContent: function(evt) { var key; key = evt.keyCode ? evt.keyCode : evt.which ? evt.which : evt.charCode; // Exclude a few keys such as Escape... if (top.ICEcoder.ctrlKeyDown==false && key!=27 && key!=20 && (key<16||key>19) && (key<37||key>40) && (key!=144||key!=145) && (key!=44||key!=45) && (key<33||key>36) && (key!=91||key!=92) && (key<112||key>123)) { ICEcoder.changedContent[ICEcoder.selectedTab-1] = 1; ICEcoder.redoTabHighlight(ICEcoder.selectedTab); } }, // Close the tab upon request closeTab: function(closeTabNum) { var cM, okToRemove; cM = ICEcoder.getcMInstance(); okToRemove = true; if (ICEcoder.changedContent[closeTabNum-1]==1) { okToRemove = confirm('You have made changes.\n\nAre you sure you want to close without saving?'); } if (okToRemove) { // recursively copy over all tabs & data from the tab to the right, if there is one for (var i=closeTabNum;i0 ? ICEcoder.selectedTab-=1 : ICEcoder.selectedTab = 0; if (ICEcoder.openFiles.length>0 && ICEcoder.selectedTab==0) {ICEcoder.selectedTab=1}; // hide the content area if we have no tabs open if (ICEcoder.openFiles.length==0) { top.document.getElementById('content').style.visibility = "hidden"; top.ICEcoder.fMIconVis('fMView',0.3); } else { // Switch the mode & the tab ICEcoder.switchMode(); ICEcoder.switchTab(ICEcoder.selectedTab); } // Highlight the selected tab after splicing the change state out of the array top.ICEcoder.changedContent.splice(closeTabNum-1,1); top.parent.ICEcoder.redoTabHighlight(ICEcoder.selectedTab); top.ICEcoder.setLastOpenedFiles(); } // Lastly, stop it from trying to also switch tab top.ICEcoder.canSwitchTabs=false; }, // Setup the file manager fileManager: function() { ICEcoder.filesFrame = top.document.getElementById('filesFrame'); if (!ICEcoder.filesFrame.contentWindow.document.getElementsByTagName) {return;}; var aMenus = ICEcoder.filesFrame.contentWindow.document.getElementsByTagName("LI"); for (var i=0; i -1) { var submenu=aMenus[i].childNodes; for (var j=0; j -1) ? "open" : "closed"; } if (submenu[j].tagName == "UL") { submenu[j].style.display = (mclass.indexOf("open") > -1) ? "block" : "none"; } } } } return false; }, // Note which files or foldets we are over on mouseover/mouseout overFileFolder: function(type, link) { ICEcoder.thisFileFolderType=type; ICEcoder.thisFileFolderLink=link; }, // Select file or folder on demand selectFileFolder: function() { var resetFile, shortURL, foundSelectedFile, foundShortURL, foundFile; // If we've clicked somewhere other than a file/folder if (top.ICEcoder.thisFileFolderLink=="") { if (!top.ICEcoder.ctrlKeyDown) { // Deselect all files for (var i=0;i<=top.ICEcoder.selectedFiles.length;i++) { if (top.ICEcoder.selectedFiles[i]) { resetFile = top.ICEcoder.filesFrame.contentWindow.document.getElementById(top.ICEcoder.selectedFiles[i]); ICEcoder.selectDeselectFile('deselect',resetFile); } } // Set our array to contain 0 items top.ICEcoder.selectedFiles.length = 0; } } else if (top.ICEcoder.thisFileFolderLink) { // We clicked a file/folder. Work out a shortened URL for the file, with pipes instead of slashes shortURL = top.ICEcoder.thisFileFolderLink.substr((top.ICEcoder.thisFileFolderLink.indexOf(shortURLStarts)+top.shortURLStarts.length),top.ICEcoder.thisFileFolderLink.length).replace(/\//g,"|"); // If we have the CTRL key down if (top.ICEcoder.ctrlKeyDown) { foundSelectedFile=false; // Deselect previously selected file? for (i=0;i<=top.ICEcoder.selectedFiles.length;i++) { if (top.ICEcoder.selectedFiles[i]==shortURL) { resetFile = ICEcoder.filesFrame.contentWindow.document.getElementById(top.ICEcoder.selectedFiles[i]); ICEcoder.selectDeselectFile('deselect',resetFile); top.ICEcoder.selectedFiles.splice(i); foundSelectedFile=true; } } if (!foundSelectedFile) { foundFile = ICEcoder.filesFrame.contentWindow.document.getElementById(shortURL); ICEcoder.selectDeselectFile('select',foundFile); top.ICEcoder.selectedFiles.push(shortURL); } // We are single clicking } else { // First deselect all files for (i=0;iCreating Folder
'+newFolder); } }, // Open a file on demand openFile: function() { if (top.ICEcoder.thisFileFolderLink!="" && top.ICEcoder.thisFileFolderType=="file") { var shortURL, canOpenFile; // work out a shortened URL for the file shortURL = top.ICEcoder.thisFileFolderLink.replace(/\|/g,"/"); shortURL = shortURL.substr((shortURL.indexOf(shortURLStarts)+shortURLStarts.length),shortURL.length); // No reason why we can't open a file (so far) canOpenFile = true; // Limit to 10 files open at a time if (top.ICEcoder.openFiles.length<10) { // check if we've already got it in our array for (var i=0;iOpening File
'+top.ICEcoder.shortURL); } else { top.ICEcoder.createNewTab(); } top.ICEcoder.fMIconVis('fMView',1); } } }, // Save a file on demand saveFile: function(saveAs) { var saveType; saveAs ? saveType = "saveAs" : saveType = "save"; top.ICEcoder.serverQueue("add","lib/file-control.php?action=save&file="+ICEcoder.openFiles[ICEcoder.selectedTab-1].replace(/\//g,"|")+"&saveType="+saveType); top.ICEcoder.serverMessage('Saving
'+ICEcoder.openFiles[ICEcoder.selectedTab-1]); }, // Prompt a rename dialog on demand renameFile: function() { var renamedFile, shortURL; shortURL = top.ICEcoder.rightClickedFile.substr((top.ICEcoder.rightClickedFile.indexOf(shortURLStarts)+top.shortURLStarts.length),top.ICEcoder.rightClickedFile.length).replace(/\|/g,"/"); renamedFile = prompt('Please enter the new name for',shortURL); if (renamedFile) { for (var i=0;i'; top.document.getElementById('tab'+(i+1)).innerHTML = top.ICEcoder.openFiles[i] + " " + closeTabLink; } } top.ICEcoder.serverQueue("add","lib/file-control.php?action=rename&file="+renamedFile+"&oldFileName="+top.ICEcoder.rightClickedFile.replace(/\|/g,"/")); top.ICEcoder.serverMessage('Renaming to
'+renamedFile); top.ICEcoder.setLastOpenedFiles(); } }, // Delete a file on demand deleteFile: function() { var delFiles, selectedFilesList; delFiles = confirm('Delete:\n\n'+top.ICEcoder.selectedFiles.toString().replace(/\|/g,"/").replace(/,/g,"\n")+'?'); // Upon supply a new name, rename tabs and update filename on server if (delFiles) { selectedFilesList = ""; for (var i=0;iDeleting File
'+top.ICEcoder.selectedFiles.toString().replace(/\|/g,"/").replace(/,/g,"\n")); }; }, // Show menu on right clicking in file manager showMenu: function() { var menuType, folderMenuItems; if ("undefined" != typeof top.ICEcoder.thisFileFolderLink && top.ICEcoder.thisFileFolderLink!="") { top.ICEcoder.selectedFiles[0].indexOf(".")>0 ? menuType = "file" : menuType = "folder"; folderMenuItems = top.document.getElementById('folderMenuItems'); menuType == "folder" ? folderMenuItems.style.display = "block" : folderMenuItems.style.display = "none"; document.getElementById('fileMenu').style.display = "inline-block"; document.getElementById('fileMenu').style.left = (top.ICEcoder.mouseX+20) + "px"; document.getElementById('fileMenu').style.top = (top.ICEcoder.mouseY-top.document.getElementById('filesFrame').contentWindow.document.body.scrollTop+80) + "px"; } return false; }, // Show & hide target element showHide: function(doVis,elem) { doVis=="show" ? elem.style.visibility='visible' : elem.style.visibility='hidden'; }, // Update find & replace options based on user selection findReplaceOptions: function() { var rText, replace, rTarget; rText = document.getElementById('rText').style.display; replace = document.getElementById('replace').style.display; rTarget = document.getElementById('rTarget').style.display; document.findAndReplace.connector.value=="and" ? document.getElementById('rText').style.display = document.getElementById('replace').style.display = document.getElementById('rTarget').style.display = "inline-block" : document.getElementById('rText').style.display = document.getElementById('replace').style.display = document.getElementById('rTarget').style.display = "none"; }, // Find & replace text according to user selections findReplace: function(action,resultsOnly) { var find, findLen, cM, content, lineCount, numChars, charsToCursor, charCount, startPos, endPos; // Determine our find string, in lowercase and the length of that find = parent.parent.document.getElementById('find').value.toLowerCase(); findLen = find.length; // If we have something to find if (findLen>0) { cM = ICEcoder.getcMInstance(); content = cM.getValue().toLowerCase(); // Find & replace the next instance? if (document.findAndReplace.connector.value=="and" && cM.getSelection()==find) { cM.replaceSelection(document.getElementById('replace').value); } if (!top.ICEcoder.findMode||parent.parent.document.getElementById('find').value!=ICEcoder.lastsearch) { ICEcoder.results = []; for (var i=0;i0) { // Show results only if (resultsOnly) { parent.parent.document.getElementById('results').innerHTML = ICEcoder.results.length + " results"; // We need to take action instead } else { lineCount=1; numChars=0; for (var i=0;iICEcoder.results.length-1) {ICEcoder.findResult=0}; parent.parent.document.getElementById('results').innerHTML = "Highlighted result "+(ICEcoder.findResult+1)+" of "+ICEcoder.results.length+" results"; lineCount=0; for (var i=0;i0) { cM.setOption("mode","javascript"); } else if (fileName.indexOf('.rb')>0) { cM.setOption("mode","ruby"); } else if (fileName.indexOf('.css')>0) { cM.setOption("mode","css"); } else { cM.setOption("mode","application/x-httpd-php"); } }, // Lock & unlock the file manager navigation on demand lockUnlockNav: function() { var lockIcon; lockIcon = top.document.getElementById('fmLock'); ICEcoder.lockedNav ? ICEcoder.lockedNav = false : ICEcoder.lockedNav = true; ICEcoder.lockedNav ? lockIcon.src="images/file-manager-icons/padlock.png" : lockIcon.src="images/file-manager-icons/padlock-disabled.png"; }, // Determine the CodeMirror instance we're using on demand getcMInstance: function(newTab) { var cM; if (newTab=="new"||(newTab!="new" && ICEcoder.openFiles.length>0)) { cM = top.ICEcoder.content.contentWindow['cM'+ICEcoder.cMInstances[ICEcoder.selectedTab-1]]; } else { cM = top.ICEcoder.content.contentWindow['cM1']; } return cM; }, // Start running plugin intervals according to given specifics startPluginIntervals: function(plugURL,plugTarget,plugTimer) { // For this window instances if (plugTarget=="_parent"||plugTarget=="_top"||plugTarget=="_self"||plugTarget=="") { setInterval('window.location=\''+plugURL+'\'',plugTimer*1000*60); // for fileControl iframe instances } else if (plugTarget.indexOf("fileControl")==0) { setInterval(function() {top.ICEcoder.serverQueue("add",plugURL);top.ICEcoder.serverMessage(plugTarget.split(":")[1]);},plugTimer*1000*60); // for _blank or named target window instances } else { setInterval('window.open(\''+plugURL+'\',\''+plugTarget+'\')',plugTimer*1000*60); } }, // Comment or uncomment line on keypress lineCommentToggle: function() { var cM, cursorPos, linePos, lineContent, lCLen, adjustCursor, startLine, endLine; cM = ICEcoder.getcMInstance(); cursorPos = cM.getCursor().ch; linePos = cM.getCursor().line; lineContent = cM.getLine(linePos); lCLen = lineContent.length; adjustCursor = 3; if (ICEcoder.caretLocType=="JavaScript"||ICEcoder.caretLocType=="PHP"||ICEcoder.caretLocType=="Ruby"||ICEcoder.caretLocType=="CSS") { if (cM.somethingSelected()) { if (ICEcoder.caretLocType=="Ruby") { startLine = cM.getCursor(true).line; endLine = cM.getCursor().line; for (var i = startLine; i<=endLine; i++) { cM.getLine(i).slice(0,2)!="# " ? cM.setLine(i, "# " + cM.getLine(i)) : cM.setLine(i, cM.getLine(i).slice(2,cM.getLine(i).length)); } } else { if (cM.getSelection().slice(0,2)!="/*") { cM.replaceSelection("/*" + cM.getSelection() + "*/"); } else { cM.replaceSelection(cM.getSelection().slice(2,cM.getSelection().length-2)); } } } else { if (ICEcoder.caretLocType=="CSS") { lineContent.slice(0,3)!="/* " ? cM.setLine(linePos, "/* " + lineContent + " */") : cM.setLine(linePos, lineContent.slice(3,lCLen).slice(0,lCLen-5)); if (lineContent.slice(0,3)=="/* ") {adjustCursor = -adjustCursor}; } else if (ICEcoder.caretLocType=="Ruby") { lineContent.slice(0,2)!="# " ? cM.setLine(linePos, "# " + lineContent) : cM.setLine(linePos, lineContent.slice(2,lCLen)); if (lineContent.slice(0,2)=="# ") {adjustCursor = -adjustCursor}; } else { lineContent.slice(0,3)!="// " ? cM.setLine(linePos, "// " + lineContent) : cM.setLine(linePos, lineContent.slice(3,lCLen)); if (lineContent.slice(0,3)=="// ") {adjustCursor = -adjustCursor}; } } } else { if (cM.somethingSelected()) { if (cM.getSelection().slice(0,4)!=""); } else { cM.replaceSelection(cM.getSelection().slice(4,cM.getSelection().length-5)); } } else { lineContent.slice(0,4)!="") : cM.setLine(linePos, lineContent.slice(4,lCLen).slice(0,lCLen-9)); lineContent.slice(0,4)=="