summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/EditingSupport.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/EditingSupport.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/EditingSupport.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/EditingSupport.js329
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+))$/
+};