diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/EditingSupport.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/EditingSupport.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/EditingSupport.js | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/EditingSupport.js b/Source/WebInspectorUI/UserInterface/Views/EditingSupport.js new file mode 100644 index 000000000..3ad9224b1 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/EditingSupport.js @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.isBeingEdited = function(element) +{ + while (element) { + if (element.__editing) + return true; + element = element.parentNode; + } + + return false; +}; + +WebInspector.markBeingEdited = function(element, value) +{ + if (value) { + if (element.__editing) + return false; + element.__editing = true; + WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1; + } else { + if (!element.__editing) + return false; + delete element.__editing; + --WebInspector.__editingCount; + } + return true; +}; + +WebInspector.isEditingAnyField = function() +{ + return !!WebInspector.__editingCount; +}; + +WebInspector.isEventTargetAnEditableField = function(event) +{ + var textInputTypes = {"text": true, "search": true, "tel": true, "url": true, "email": true, "password": true}; + if (event.target instanceof HTMLInputElement) + return event.target.type in textInputTypes; + + var codeMirrorEditorElement = event.target.enclosingNodeOrSelfWithClass("CodeMirror"); + if (codeMirrorEditorElement && codeMirrorEditorElement.CodeMirror) + return !codeMirrorEditorElement.CodeMirror.getOption("readOnly"); + + if (event.target instanceof HTMLTextAreaElement) + return true; + + if (event.target.enclosingNodeOrSelfWithClass("text-prompt")) + return true; + + if (WebInspector.isBeingEdited(event.target)) + return true; + + return false; +}; + +WebInspector.EditingConfig = class EditingConfig +{ + constructor(commitHandler, cancelHandler, context) + { + this.commitHandler = commitHandler; + this.cancelHandler = cancelHandler; + this.context = context; + this.spellcheck = false; + } + + setPasteHandler(pasteHandler) + { + this.pasteHandler = pasteHandler; + } + + setMultiline(multiline) + { + this.multiline = multiline; + } + + setCustomFinishHandler(customFinishHandler) + { + this.customFinishHandler = customFinishHandler; + } + + setNumberCommitHandler(numberCommitHandler) + { + this.numberCommitHandler = numberCommitHandler; + } +}; + +WebInspector.startEditing = function(element, config) +{ + if (!WebInspector.markBeingEdited(element, true)) + return null; + + config = config || new WebInspector.EditingConfig(function() {}, function() {}); + var committedCallback = config.commitHandler; + var cancelledCallback = config.cancelHandler; + var pasteCallback = config.pasteHandler; + var context = config.context; + var oldText = getContent(element); + var moveDirection = ""; + + element.classList.add("editing"); + + var oldSpellCheck = element.hasAttribute("spellcheck") ? element.spellcheck : undefined; + element.spellcheck = config.spellcheck; + + if (config.multiline) + element.classList.add("multiline"); + + var oldTabIndex = element.tabIndex; + if (element.tabIndex < 0) + element.tabIndex = 0; + + function blurEventListener() { + editingCommitted.call(element); + } + + function getContent(element) { + if (element.tagName === "INPUT" && element.type === "text") + return element.value; + else + return element.textContent; + } + + function cleanUpAfterEditing() + { + WebInspector.markBeingEdited(element, false); + + this.classList.remove("editing"); + this.scrollTop = 0; + this.scrollLeft = 0; + + if (oldSpellCheck === undefined) + element.removeAttribute("spellcheck"); + else + element.spellcheck = oldSpellCheck; + + if (oldTabIndex === -1) + this.removeAttribute("tabindex"); + else + this.tabIndex = oldTabIndex; + + element.removeEventListener("blur", blurEventListener, false); + element.removeEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.removeEventListener("paste", pasteEventListener, true); + + WebInspector.restoreFocusFromElement(element); + } + + function editingCancelled() + { + if (this.tagName === "INPUT" && this.type === "text") + this.value = oldText; + else + this.textContent = oldText; + + cleanUpAfterEditing.call(this); + + cancelledCallback(this, context); + } + + function editingCommitted() + { + cleanUpAfterEditing.call(this); + + committedCallback(this, getContent(this), oldText, context, moveDirection); + } + + function defaultFinishHandler(event) + { + var hasOnlyMetaModifierKey = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; + if (isEnterKey(event) && (!config.multiline || hasOnlyMetaModifierKey)) + return "commit"; + else if (event.keyCode === WebInspector.KeyboardShortcut.Key.Escape.keyCode || event.keyIdentifier === "U+001B") + return "cancel"; + else if (event.keyIdentifier === "U+0009") // Tab key + return "move-" + (event.shiftKey ? "backward" : "forward"); + else if (event.altKey) { + if (event.keyIdentifier === "Up" || event.keyIdentifier === "Down") + return "modify-" + (event.keyIdentifier === "Up" ? "up" : "down"); + if (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown") + return "modify-" + (event.keyIdentifier === "PageUp" ? "up-big" : "down-big"); + } + } + + function handleEditingResult(result, event) + { + if (result === "commit") { + editingCommitted.call(element); + event.preventDefault(); + event.stopPropagation(); + } else if (result === "cancel") { + editingCancelled.call(element); + event.preventDefault(); + event.stopPropagation(); + } else if (result && result.startsWith("move-")) { + moveDirection = result.substring(5); + if (event.keyIdentifier !== "U+0009") + blurEventListener(); + } else if (result && result.startsWith("modify-")) { + let direction = result.substring(7); + let modifyValue = direction.startsWith("up") ? 1 : -1; + if (direction.endsWith("big")) + modifyValue *= 10; + + if (event.shiftKey) + modifyValue *= 10; + else if (event.ctrlKey) + modifyValue /= 10; + + let selection = element.ownerDocument.defaultView.getSelection(); + if (!selection.rangeCount) + return; + + let range = selection.getRangeAt(0); + if (!range.commonAncestorContainer.isSelfOrDescendant(element)) + return false; + + let wordRange = range.startContainer.rangeOfWord(range.startOffset, WebInspector.EditingSupport.StyleValueDelimiters, element); + let word = wordRange.toString(); + let wordPrefix = ""; + let wordSuffix = ""; + let nonNumberInWord = /[^\d-\.]+/.exec(word); + if (nonNumberInWord) { + let nonNumberEndOffset = nonNumberInWord.index + nonNumberInWord[0].length; + if (range.startOffset > wordRange.startOffset + nonNumberInWord.index && nonNumberEndOffset < word.length && range.startOffset !== wordRange.startOffset) { + wordPrefix = word.substring(0, nonNumberEndOffset); + word = word.substring(nonNumberEndOffset); + } else { + wordSuffix = word.substring(nonNumberInWord.index); + word = word.substring(0, nonNumberInWord.index); + } + } + + let matches = WebInspector.EditingSupport.CSSNumberRegex.exec(word); + if (!matches || matches.length !== 4) + return; + + let replacement = matches[1] + (Math.round((parseFloat(matches[2]) + modifyValue) * 100) / 100) + matches[3]; + + selection.removeAllRanges(); + selection.addRange(wordRange); + document.execCommand("insertText", false, wordPrefix + replacement + wordSuffix); + + let container = range.commonAncestorContainer; + let startOffset = range.startOffset; + // This check is for the situation when the cursor is in the space between the + // opening quote of the attribute and the first character. In that spot, the + // commonAncestorContainer is actually the entire attribute node since `="` is + // added as a simple text node. Since the opening quote is immediately before + // the attribute, the node for that attribute must be the next sibling and the + // text of the attribute's value must be the first child of that sibling. + if (container.parentNode.classList.contains("editing")) { + container = container.nextSibling.firstChild; + startOffset = 0; + } + startOffset += wordPrefix.length; + + if (!container) + return; + + let replacementSelectionRange = document.createRange(); + replacementSelectionRange.setStart(container, startOffset); + replacementSelectionRange.setEnd(container, startOffset + replacement.length); + + selection.removeAllRanges(); + selection.addRange(replacementSelectionRange); + + if (typeof config.numberCommitHandler === "function") + config.numberCommitHandler(element, getContent(element), oldText, context, moveDirection); + + event.preventDefault(); + } + } + + function pasteEventListener(event) + { + var result = pasteCallback(event); + handleEditingResult(result, event); + } + + function keyDownEventListener(event) + { + var handler = config.customFinishHandler || defaultFinishHandler; + var result = handler(event); + handleEditingResult(result, event); + } + + element.addEventListener("blur", blurEventListener, false); + element.addEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.addEventListener("paste", pasteEventListener, true); + + element.focus(); + + return { + cancel: editingCancelled.bind(element), + commit: editingCommitted.bind(element) + }; +}; + +WebInspector.EditingSupport = { + StyleValueDelimiters: " \xA0\t\n\"':;,/()", + CSSNumberRegex: /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/, + NumberRegex: /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/ +}; |