From 074ed41db00477eb2e8a302aff32c6765043a996 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Fri, 12 Oct 2012 22:59:51 +1100 Subject: [PATCH 1/8] Clipboard API support, context menu Delete command support --- lib/ace/editor.js | 11 +- lib/ace/keyboard/textinput.js | 276 ++++++++++++++++------------------ 2 files changed, 136 insertions(+), 151 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 937e0e73..340e8e7f 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -656,6 +656,15 @@ var Editor = function(renderer, session) { this.insert(text); }; + /** + * Editor.onDelete() + * + * called whenever a text "delete" happens. + **/ + this.onDelete = function() { + this.commands.exec("del", this); + }; + /** * Editor.insert(text) * - text (String): The new text to add @@ -2126,4 +2135,4 @@ var Editor = function(renderer, session) { exports.Editor = Editor; -}); \ No newline at end of file +}); diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 7204c022..876872f0 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -54,69 +54,46 @@ var TextInput = function(parentNode, host) { parentNode.insertBefore(text, parentNode.firstChild); var PLACEHOLDER = useragent.isIE ? "\x01" : "\x00"; - reset(true); if (isFocused()) host.onFocus(); - var inCompostion = false; - var copied = false; var pasted = false; + var inCompostion = false; + var isSelectionEmpty = true; var tempStyle = ''; - function reset(full) { - try { - if (full) { - text.value = PLACEHOLDER; - text.selectionStart = 0; - text.selectionEnd = 1; - } else - text.select(); - } catch (e) {} - } - - function sendText(valueToSend) { - if (!copied) { - var value = valueToSend || text.value; - if (value) { - if (value.length > 1) { - if (value.charAt(0) == PLACEHOLDER) - value = value.substr(1); - else if (value.charAt(value.length - 1) == PLACEHOLDER) - value = value.slice(0, -1); - } - - if (value && value != PLACEHOLDER) { - if (pasted) - host.onPaste(value); - else - host.onTextInput(value); - } - } + host.addEventListener('changeSelection', function(){ + if (host.selection.isEmpty() != isSelectionEmpty) { + isSelectionEmpty = !isSelectionEmpty; + text.value = isSelectionEmpty ? '' : PLACEHOLDER; + text.select(); } + }); - copied = false; - pasted = false; + var onInput = function(e) { + if (inCompostion) + return; - // Safari doesn't fire copy events if no text is selected - reset(true); - } + if (pasted) { + var data = text.value; + if (data) + host.onPaste(data); + pasted = false; + } else { + var data = text.value; + if (data) + host.onTextInput(data); + else + host.onDelete(); + } + text.value = ""; - var onTextInput = function(e) { - if (!inCompostion) - sendText(e.data); - setTimeout(function () { - if (!inCompostion) - reset(true); - }, 0); - }; - - var onPropertyChange = function(e) { - setTimeout(function() { - if (!inCompostion) - if(text.value != "") { - sendText(); - } - }, 0); + //http://code.google.com/p/chromium/issues/detail?id=76516 + if (useragent.isWebKit) + setTimeout(function(){ + text.blur(); + text.focus(); + }); }; var onCompositionStart = function(e) { @@ -135,37 +112,106 @@ var TextInput = function(parentNode, host) { host.onCompositionEnd(); }; - var onCopy = function(e) { - copied = true; - var copyText = host.getCopyText(); - if(copyText) - text.value = copyText; - else - e.preventDefault(); - reset(); - setTimeout(function () { - sendText(); - }, 0); + var onCut = function(e) { + var data = host.getCopyText(); + if (!data) { + event.preventDefault(e); + return; + } + + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + // Safari 5 has clipboardData object, but does not handle setData() + var supported = e.clipboardData.setData("Text", data); + if (supported) { + host.onCut(); + event.preventDefault(e); + } + } + + if (!supported) { + text.value = data; + text.select(); + setTimeout(function(){ host.onCut() }); + } }; - var onCut = function(e) { - copied = true; - var copyText = host.getCopyText(); - if(copyText) { - text.value = copyText; - host.onCut(); - } else - e.preventDefault(); - reset(); - setTimeout(function () { - sendText(); - }, 0); + var onCopy = function(e) { + var data = host.getCopyText(); + if (!data) { + event.preventDefault(e); + return; + } + + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + // Safari 5 has clipboardData object, but does not handle setData() + var supported = e.clipboardData.setData("Text", data); + if (supported) { + host.onCopy(); + event.preventDefault(e); + } + } + if (!supported) { + text.value = data; + text.select(); + setTimeout(function(){ host.onCopy() }); + } + + + }; + + var onPaste = function(e) { + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + var data = e.clipboardData.getData("Text"); + if (data) + host.onPaste(data); + event.preventDefault(e); + } + else { + pasted = true; + } }; event.addCommandKeyListener(text, host.onCommandKey.bind(host)); - event.addListener(text, "input", onTextInput); - + + event.addListener(text, "input", onInput); + + event.addListener(text, "cut", onCut); + event.addListener(text, "copy", onCopy); + event.addListener(text, "paste", onPaste); + + + // Opera has no clipboard events + if (!('oncut' in text) || !('oncopy' in text) || !('onpaste' in text)){ + event.addListener(parentNode, "keydown", function(e) { + if ((useragent.isMac && !e.metaKey) || !e.ctrlKey) + return; + + switch (e.keyCode) { + case 67: + onCopy(e); + break; + case 86: + onPaste(e); + break; + case 88: + onCut(e); + break; + } + }); + } + if (useragent.isOldIE) { + event.addListener(text, "propertychange", function(e){ + if (text.value != "" && text.value != PLACEHOLDER) + onInput(e); + }); + var keytable = { 13:1, 27:1 }; event.addListener(text, "keyup", function (e) { if (inCompostion && (!text.value || keytable[e.keyCode])) @@ -175,70 +221,6 @@ var TextInput = function(parentNode, host) { } inCompostion ? onCompositionUpdate() : onCompositionStart(); }); - - event.addListener(text, "propertychange", function() { - if (text.value != PLACEHOLDER) - setTimeout(sendText, 0); - }); - } - - event.addListener(text, "paste", function(e) { - // Mark that the next input text comes from past. - pasted = true; - // Some browsers support the event.clipboardData API. Use this to get - // the pasted content which increases speed if pasting a lot of lines. - if (e.clipboardData && e.clipboardData.getData) { - sendText(e.clipboardData.getData("text/plain")); - e.preventDefault(); - } - else { - // If a browser doesn't support any of the things above, use the regular - // method to detect the pasted input. - onPropertyChange(); - } - }); - - if ("onbeforecopy" in text && typeof clipboardData !== "undefined") { - event.addListener(text, "beforecopy", function(e) { - if (tempStyle) - return; // without this text is copied when contextmenu is shown - var copyText = host.getCopyText(); - if (copyText) - clipboardData.setData("Text", copyText); - else - e.preventDefault(); - }); - event.addListener(parentNode, "keydown", function(e) { - if (e.ctrlKey && e.keyCode == 88) { - var copyText = host.getCopyText(); - if (copyText) { - clipboardData.setData("Text", copyText); - host.onCut(); - } - event.preventDefault(e); - } - }); - event.addListener(text, "cut", onCut); // for ie9 context menu - } - else if (useragent.isOpera && !("KeyboardEvent" in window)) { - event.addListener(parentNode, "keydown", function(e) { - if ((useragent.isMac && !e.metaKey) || !e.ctrlKey) - return; - - if ((e.keyCode == 88 || e.keyCode == 67)) { - var copyText = host.getCopyText(); - if (copyText) { - text.value = copyText; - text.select(); - if (e.keyCode == 88) - host.onCut(); - } - } - }); - } - else { - event.addListener(text, "copy", onCopy); - event.addListener(text, "cut", onCut); } event.addListener(text, "compositionstart", onCompositionStart); @@ -256,11 +238,11 @@ var TextInput = function(parentNode, host) { event.addListener(text, "focus", function() { host.onFocus(); - reset(); + text.select(); }); this.focus = function() { - reset(); + text.select(); text.focus(); }; @@ -286,11 +268,6 @@ var TextInput = function(parentNode, host) { (useragent.isIE ? "background:rgba(0, 0, 0, 0.03); opacity:0.1;" : "") + //"background:rgba(250, 0, 0, 0.3); opacity:1;" + "left:" + (e.clientX - 2) + "px; top:" + (e.clientY - 2) + "px;"; - if (host.selection.isEmpty()) - text.value = ""; - else - reset(true); - if (e.type != "mousedown") return; @@ -311,7 +288,6 @@ var TextInput = function(parentNode, host) { text.style.cssText = tempStyle; tempStyle = ''; } - sendText(); if (host.renderer.$keepTextAreaAtCursor == null) { host.renderer.$keepTextAreaAtCursor = true; host.renderer.$moveTextAreaToCursor(); From 0604b753108a15bcb508aa54358b1cadbbccf7e8 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sat, 20 Oct 2012 19:21:32 +1100 Subject: [PATCH 2/8] change selection instead of text.value --- lib/ace/keyboard/textinput.js | 81 +++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 876872f0..7889a09a 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -53,22 +53,54 @@ var TextInput = function(parentNode, host) { text.style.top = "-2em"; parentNode.insertBefore(text, parentNode.firstChild); - var PLACEHOLDER = useragent.isIE ? "\x01" : "\x00"; + var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01" : "\x00"; + + resetValue(); + if (isFocused()) host.onFocus(); + // Somehow fixes problem with firing onpropertychange on first typed char + if (useragent.isOldIE) { + resetSelection(); + resetValue(); + setTimeout(resetSelection); + } + var pasted = false; + var inCompostion = false; + var isSelectionEmpty = true; + var tempStyle = ''; - host.addEventListener('changeSelection', function(){ - if (host.selection.isEmpty() != isSelectionEmpty) { - isSelectionEmpty = !isSelectionEmpty; - text.value = isSelectionEmpty ? '' : PLACEHOLDER; - text.select(); + function resetValue() { + //http://code.google.com/p/chromium/issues/detail?id=76516 + if (!useragent.isWebKit) + text.value = PLACEHOLDER; + else + setTimeout(function(){ + text.value = PLACEHOLDER; + }); + }; + + function resetSelection() { + var selectionStart = isSelectionEmpty ? 1 : 0; + var selectionEnd = 1; + + if (text.setSelectionRange) { + text.setSelectionRange(selectionStart, selectionEnd); } - }); + // IE8 does not support setSelectionRange + else if (text.createTextRange) { + var range = text.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } + }; var onInput = function(e) { if (inCompostion) @@ -76,24 +108,19 @@ var TextInput = function(parentNode, host) { if (pasted) { var data = text.value; + resetValue(); if (data) host.onPaste(data); pasted = false; - } else { - var data = text.value; - if (data) - host.onTextInput(data); - else - host.onDelete(); + return; } - text.value = ""; - //http://code.google.com/p/chromium/issues/detail?id=76516 - if (useragent.isWebKit) - setTimeout(function(){ - text.blur(); - text.focus(); - }); + var data = text.value.substring(isSelectionEmpty ? 1 : 0); + resetValue(); + if (data) + host.onTextInput(data); + else + host.onDelete(); }; var onCompositionStart = function(e) { @@ -173,6 +200,7 @@ var TextInput = function(parentNode, host) { event.preventDefault(e); } else { + text.value = ""; pasted = true; } }; @@ -238,11 +266,10 @@ var TextInput = function(parentNode, host) { event.addListener(text, "focus", function() { host.onFocus(); - text.select(); + resetSelection(); }); this.focus = function() { - text.select(); text.focus(); }; @@ -300,8 +327,16 @@ var TextInput = function(parentNode, host) { if (!useragent.isGecko) event.addListener(text, "contextmenu", function(e) { host.textInput.onContextMenu(e); - onContextMenuClose() + onContextMenuClose(); }); + + + host.addEventListener('changeSelection', function(){ + if (host.selection.isEmpty() != isSelectionEmpty) { + isSelectionEmpty = !isSelectionEmpty; + resetSelection(); + } + }); }; exports.TextInput = TextInput; From 473ddfd353734b49691432eff97f7ada2f4e0058 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sat, 20 Oct 2012 19:22:34 +1100 Subject: [PATCH 3/8] Add Select All context menu command support --- lib/ace/keyboard/textinput.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 7889a09a..2082510a 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -53,7 +53,7 @@ var TextInput = function(parentNode, host) { text.style.top = "-2em"; parentNode.insertBefore(text, parentNode.firstChild); - var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01" : "\x00"; + var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01\x01" : "\x00\x00"; resetValue(); @@ -67,6 +67,8 @@ var TextInput = function(parentNode, host) { setTimeout(resetSelection); } + var cut = false + var copied = false; var pasted = false; var inCompostion = false; @@ -86,8 +88,8 @@ var TextInput = function(parentNode, host) { }; function resetSelection() { - var selectionStart = isSelectionEmpty ? 1 : 0; - var selectionEnd = 1; + var selectionStart = isSelectionEmpty ? 2 : 1; + var selectionEnd = 2; if (text.setSelectionRange) { text.setSelectionRange(selectionStart, selectionEnd); @@ -102,6 +104,21 @@ var TextInput = function(parentNode, host) { } }; + var onSelect = function(e) { + if (cut) { + cut = false; + return; + } + if (copied) { + copied = false; + return; + } + if (text.selectionStart === 0 && text.selectionEnd === text.value.length) { + host.selectAll(); + resetSelection(); + } + }; + var onInput = function(e) { if (inCompostion) return; @@ -115,7 +132,7 @@ var TextInput = function(parentNode, host) { return; } - var data = text.value.substring(isSelectionEmpty ? 1 : 0); + var data = text.value.substring(isSelectionEmpty ? 2 : 1); resetValue(); if (data) host.onTextInput(data); @@ -160,6 +177,7 @@ var TextInput = function(parentNode, host) { if (!supported) { text.value = data; text.select(); + cut = true; setTimeout(function(){ host.onCut() }); } }; @@ -184,6 +202,7 @@ var TextInput = function(parentNode, host) { if (!supported) { text.value = data; text.select(); + copided = true; setTimeout(function(){ host.onCopy() }); } @@ -207,6 +226,8 @@ var TextInput = function(parentNode, host) { event.addCommandKeyListener(text, host.onCommandKey.bind(host)); + event.addListener(text, "select", onSelect); + event.addListener(text, "input", onInput); event.addListener(text, "cut", onCut); From 38ef17528bd58be42df13a6c18dab5556a5df6e6 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 03:40:07 +1100 Subject: [PATCH 4/8] Fix mistype and opera copy/cut selectAll triggering --- lib/ace/keyboard/textinput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 2082510a..58c49967 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -175,9 +175,9 @@ var TextInput = function(parentNode, host) { } if (!supported) { + cut = true; text.value = data; text.select(); - cut = true; setTimeout(function(){ host.onCut() }); } }; @@ -200,9 +200,9 @@ var TextInput = function(parentNode, host) { } } if (!supported) { + copied = true; text.value = data; text.select(); - copided = true; setTimeout(function(){ host.onCopy() }); } From a49a0b9ad3bcc946b997c7e5fe6d483a626c34c1 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 04:48:12 +1100 Subject: [PATCH 5/8] Fix webkit text.oninput value changing workaround --- lib/ace/keyboard/textinput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 58c49967..05df68fa 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -78,12 +78,12 @@ var TextInput = function(parentNode, host) { var tempStyle = ''; function resetValue() { + text.value = PLACEHOLDER; //http://code.google.com/p/chromium/issues/detail?id=76516 - if (!useragent.isWebKit) - text.value = PLACEHOLDER; - else + if (useragent.isWebKit) setTimeout(function(){ text.value = PLACEHOLDER; + resetSelection(); }); }; From fcbe904435fb9026c95b077a967c8004e217eba1 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 12:59:25 +1100 Subject: [PATCH 6/8] fix clipboardData reference --- lib/ace/keyboard/textinput.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 05df68fa..1a9bd3c5 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -163,11 +163,11 @@ var TextInput = function(parentNode, host) { return; } - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { + if (clipboardData) { // Safari 5 has clipboardData object, but does not handle setData() - var supported = e.clipboardData.setData("Text", data); + var supported = clipboardData.setData("Text", data); if (supported) { host.onCut(); event.preventDefault(e); @@ -189,11 +189,11 @@ var TextInput = function(parentNode, host) { return; } - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { + if (clipboardData) { // Safari 5 has clipboardData object, but does not handle setData() - var supported = e.clipboardData.setData("Text", data); + var supported = clipboardData.setData("Text", data); if (supported) { host.onCopy(); event.preventDefault(e); @@ -210,10 +210,10 @@ var TextInput = function(parentNode, host) { }; var onPaste = function(e) { - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { - var data = e.clipboardData.getData("Text"); + if (clipboardData) { + var data = clipboardData.getData("Text"); if (data) host.onPaste(data); event.preventDefault(e); From 00f6b46cd3e50a48a114f59ef65fd4605e9a1512 Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 26 Oct 2012 01:45:32 +0400 Subject: [PATCH 7/8] disable autocapitalize to improve behavior on mobile browsers --- lib/ace/keyboard/textinput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 1a9bd3c5..970a5ed0 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -48,6 +48,8 @@ var TextInput = function(parentNode, host) { text.setAttribute("x-palm-disable-auto-cap", true); text.wrap = "off"; + text.autocorrect = "off"; + text.autocapitalize = "off"; text.spellcheck = false; text.style.top = "-2em"; From 3677c36a14105cfd29ac7fda76d91ea743b2d25e Mon Sep 17 00:00:00 2001 From: nightwing Date: Fri, 26 Oct 2012 01:50:45 +0400 Subject: [PATCH 8/8] add editor.execCommand function --- lib/ace/editor.js | 10 +++------- lib/ace/keyboard/textinput.js | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 340e8e7f..67a296f8 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -656,13 +656,9 @@ var Editor = function(renderer, session) { this.insert(text); }; - /** - * Editor.onDelete() - * - * called whenever a text "delete" happens. - **/ - this.onDelete = function() { - this.commands.exec("del", this); + + this.execCommand = function(command, args) { + this.commands.exec(command, this, args); }; /** diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 970a5ed0..1e215071 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -139,7 +139,7 @@ var TextInput = function(parentNode, host) { if (data) host.onTextInput(data); else - host.onDelete(); + host.execCommand("del", {source: "ace"}); }; var onCompositionStart = function(e) {