diff --git a/README.mdown b/README.mdown index 4edada5..334f7e7 100644 --- a/README.mdown +++ b/README.mdown @@ -34,6 +34,7 @@ Changelog - Script execution is now done via an async js request, preventing die() and exception to mess up the entire console - Added a status bar with char/line display - Added a toggle button to expand/collapse all krumo sub-trees at once + - Cross-browser compatibility enhancements - 1.0.0 - Initial Public Release diff --git a/jquery.selections.js b/jquery.selections.js index 85bc897..562a159 100644 --- a/jquery.selections.js +++ b/jquery.selections.js @@ -16,7 +16,6 @@ * Source on Github http://github.com/Seldaek/jquery-selections */ (function($) { - /** * sets the caret position * @@ -24,8 +23,19 @@ * @see $.fn.setSelection */ $.fn.setCaret = function(index) { + var range, elem; + elem = this.get(0); + + if ($.browser.opera) { + match = this.val().match(/\n/g); + if (match) { + newlines = match.length; + index += newlines; + } + } + this.setSelection(index); - } + }; /** * returns the caret position, or 0 if the element has no caret @@ -33,28 +43,58 @@ * @return int */ $.fn.getCaret = function() { - var elem, range, elemRange, elemRangeCopy; + var elem, range, elemRange, elemRangeCopy, value, caret, pos; elem = this.get(0); + value = $(elem).val(); + + // standard browsers if (elem.selectionStart) { - return elem.selectionStart; + caret = elem.selectionStart; } else if (document.selection) { + // old IE handling elem.focus(); - range = document.selection.createRange(); - if (range === null) { - return 0; - } - elemRange = elem.createTextRange(), - elemRangeCopy = elemRange.duplicate(); - elemRange.moveToBookmark(range.getBookmark()); - elemRangeCopy.setEndPoint('EndToStart', elemRange); + // handle input texts + if (elem.nodeName === 'INPUT') { + elemRange = elem.createTextRange(); + elemRangeCopy = elemRange.duplicate(); + elemRange.moveToBookmark(range.getBookmark()); + elemRangeCopy.setEndPoint('EndToStart', elemRange); + caret = elemRangeCopy.text.length; + } else { + // handle textareas + elemRangeCopy = range.duplicate(); + elemRangeCopy.moveToElementText(elem); + + pos = 0; + if (range.text.length > 1) { + pos = Math.max(0, pos - range.text.length); + } + + caret = -1 + pos; + elemRangeCopy .moveStart('character', pos); - return elemRangeCopy.text.length; + while (elemRangeCopy.inRange(range)) { + elemRangeCopy.moveStart('character'); + caret++; + } + } + } else { + caret = 0; + } + + if ($.browser.opera) { + value = value.replace(/\r?\n/g, "\r\n").substr(0, caret); + match = value.match(/\r\n/g); + if (match) { + newlines = match.length; + caret -= newlines; + } } - return 0; + return caret; }; /** @@ -69,10 +109,12 @@ elem = this.get(0); end = end || start; + // standard browsers if (elem.setSelectionRange) { elem.focus(); elem.setSelectionRange(start, end); } else if (elem.createTextRange) { + // old IE handling range = elem.createTextRange(); range.collapse(true); range.moveEnd('character', end); @@ -83,6 +125,55 @@ return this; }; + /** + * reads the text the user selected + * + * @param int start index of the beginning of the selection + * @param int end index of the end of the selection + * @return string + */ + $.fn.getSelectedText = function() { + var elem = this.get(0); + + // standard browsers + if (elem.selectionStart) { + return elem.value.substr(elem.selectionStart, elem.selectionEnd); + } + + // old IE + if (document.selection) { + elem.focus(); + return document.selection.createRange().text; + } + + return ''; + }; + + /** + * injects the given text in place of the current selection + * + * @param string text + * @return object chainable + */ + $.fn.replaceSelection = function(text) { + var elem = this.get(0); + + // standard browsers + if (elem.selectionStart) { + elem.value = elem.value.substr(0, elem.selectionStart) + text + elem.value.substr(elem.selectionEnd, elem.value.length); + return this; + } + + // old IE + if (document.selection) { + elem.focus(); + document.selection.createRange().text = text; + return this; + } + + return this; + }; + /** * injects the given text at the current caret position * @@ -99,6 +190,5 @@ $elem.setCaret(caret + text.length); return this; - } - -}(jQuery)); \ No newline at end of file + }; +}(jQuery)); diff --git a/php-console.js b/php-console.js index dff1f1a..02bc706 100644 --- a/php-console.js +++ b/php-console.js @@ -13,59 +13,71 @@ */ $(function() { - var updateStatusBar; + var updateStatusBar, handleKeyPress; // updates the text of the status bar updateStatusBar = function() { - var caret, part, matches; + var caret, part, matches, charCount, lineCount; caret = $('textarea[name="code"]').getCaret(); - part = $('textarea[name="code"]').val().substring(0, caret); - matches = part.match(/(\r?\n)?([^\r\n]*)/g); + part = $('textarea[name="code"]').val().substr(0, caret); + matches = part.match(/(\n|[^\r\n]*$)/g); part = matches.length > 1 ? matches[matches.length - 2] : matches[0]; - $('.statusbar').text('Line: ' + Math.max(1, matches.length-1) + ', Column: ' + (matches.length > 2 ? part.length : part.length + 1)); + lineCount = Math.max(1, matches.length); + // matched the first char of a line, so matches are only \n's + if (part === "" || part === "\r\n" || part === "\n") { + charCount = 1; + } else { + // matched another char, so we've got the current line as the next-to-last match + charCount = part.length + 1; + lineCount--; + } + $('.statusbar').text('Line: ' + lineCount + ', Column: ' + charCount); }; - $('textarea[name="code"]') - .keydown(function(e) { - var caret, part, matches; - switch(e.keyCode) { - case 9: - // add 4 spaces when tab is pressed + handleKeyPress = function(e) { + var caret, part, matches; + switch(e.keyCode) { + case 9: + // add 4 spaces when tab is pressed + e.preventDefault(); + $(this).injectText(" "); + break; + case 13: + // submit form on ctrl-enter or alt-enter + if (e.metaKey || e.altKey) { e.preventDefault(); - $(this).injectText(" "); - break; - case 13: - // submit form on ctrl-enter or alt-enter - if (e.metaKey || e.altKey) { - e.preventDefault(); - $('form').submit(); - return; - } + $('form').submit(); + return; + } - // indent automatically the new lines - // skip because buggy in opera until they fix the preventDefault bug - if ($.browser.opera) { - return; - } - caret = $(this).getCaret(); - part = $(this).val().substring(0, caret); - matches = part.match(/(\r?\n +)[^\r\n]*$/); - if (matches) { - $(this).val(function(idx, val) { - return val.substring(0, caret) + matches[1] + val.substring(caret); - }); - $(this).setCaret(caret + matches[1].length); - e.preventDefault(); - } - break; + // indent automatically the new lines + caret = $(this).getCaret(); + part = $(this).val().substr(0, caret); + matches = part.match(/(\n +)[^\r\n]*$/); + if (matches) { + $(this).val(function(idx, val) { + return val.substring(0, caret) + matches[1] + val.substring(caret); + }); + $(this).setCaret(caret + matches[1].length); + e.preventDefault(); } + break; + } + + updateStatusBar(); + }; - updateStatusBar(); - }) + $('textarea[name="code"]') .keyup(updateStatusBar) .click(updateStatusBar) .focus(); + if ($.browser.opera) { + $('textarea[name="code"]').keypress(handleKeyPress); + } else { + $('textarea[name="code"]').keydown(handleKeyPress); + } + updateStatusBar(); $('input[name="subm"]').keyup(function(e) {