From 2e04af953463643791f6362bd8ef4c6ba190abfa Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Wed, 18 Apr 2012 13:48:51 -0700 Subject: Squashed commit of the following: commit 2054551bfb01a0f4ca2e138b9d724835462d45cd Merge: 765c2da 616a853 Author: Valerio Virgillito Date: Wed Apr 18 13:48:21 2012 -0700 Merge branch 'refs/heads/master' into integration commit 765c2da8e1aa03550caf42b2bd5f367555ad2843 Author: Valerio Virgillito Date: Tue Apr 17 15:29:41 2012 -0700 updating the picasa carousel Signed-off-by: Valerio Virgillito commit 9484f1c82b81e27edf2dc0a1bcc1fa3b12077406 Merge: d27f2df cacb4a2 Author: Valerio Virgillito Date: Tue Apr 17 15:03:50 2012 -0700 Merge branch 'refs/heads/master' into integration commit d27f2df4d846064444263d7832d213535962abe7 Author: Valerio Virgillito Date: Wed Apr 11 10:39:36 2012 -0700 integrating new picasa carousel component Signed-off-by: Valerio Virgillito commit 6f98384c9ecbc8abe55ccfe1fc25a0c7ce22c493 Author: Valerio Virgillito Date: Tue Apr 10 14:33:00 2012 -0700 fixed the text area case issue Text area was renamed from TextArea to Textarea Signed-off-by: Valerio Virgillito commit 1e83e26652266136802bc7af930379c1ecd631a6 Author: Valerio Virgillito Date: Mon Apr 9 22:10:45 2012 -0700 integrating montage v0.8 into ninja. Signed-off-by: Valerio Virgillito Signed-off-by: Valerio Virgillito --- .../rich-text-linkpopup.css | 31 + .../rich-text-linkpopup.html | 37 + .../rich-text-linkpopup.js | 274 ++++ .../rich-text-resizer.reel/rich-text-resizer.css | 141 ++ .../rich-text-resizer.reel/rich-text-resizer.html | 45 + .../rich-text-resizer.reel/rich-text-resizer.js | 568 +++++++ .../rich-text-editor.reel/rich-text-editor-base.js | 1706 ++++++++++++++++++++ .../rich-text-editor.reel/rich-text-editor.css | 30 + .../rich-text-editor.reel/rich-text-editor.html | 41 + .../rich-text-editor.reel/rich-text-editor.js | 614 +++++++ .../rich-text-editor.reel/rich-text-sanitizer.js | 132 ++ 11 files changed, 3619 insertions(+) create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.css create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.html create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.js create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.css create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.html create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor-base.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.css create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.html create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-sanitizer.js (limited to 'node_modules/montage/ui/rich-text-editor') diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.css b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.css new file mode 100644 index 00000000..db04d0ff --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.css @@ -0,0 +1,31 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + +.montage-link-popup { + position: absolute; + padding: 12px 20px; + border: 1px solid; + + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.2); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,.2); + box-shadow: 0 1px 3px rgba(0,0,0,.2); + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + border-color: #BBB #BBB #A8A8A8; + + background-color: white; + color: #666; + font: 13px/normal "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + + cursor: default; +} + +.montage-link-popup a { + cursor: pointer; + text-decoration: none; + color: #15C; +} \ No newline at end of file diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.html b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.html new file mode 100644 index 00000000..1e2ec2cc --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.js b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.js new file mode 100644 index 00000000..90342d81 --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.js @@ -0,0 +1,274 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module "montage/ui/rich-text-editor/overlays/rich-text-resizer.reel" + @requires montage/core/core + @requires montage/ui/component +*/ +var Montage = require("montage").Montage, + Component = require("ui/component").Component; + +/** + @class module:"montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel".RichTextLinkPopup + @extends module:montage/ui/component.Component +*/ +exports.RichTextLinkPopup = Montage.create(Component,/** @lends module:"montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel".RichTextLinkPopup# */ { + + /** + Description TODO + @private + */ + _isActive: { + enumerable: false, + value: false + }, + /** + Description TODO + @private + */ + _editor: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + target: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _needsReset: { + enumerable: false, + value: false + }, + + /** + Description TODO + @type {Function} + */ + initWithEditor: { + value: function(editor) { + this._editor = editor; + } + }, + + /** + Description TODO + @type {Function} + */ + editorMouseUp: { + value: function(event) { + var element; + + if (this._editor.activeOverlay != this) { + // Check if the caret is inside an image within an anchor element + if (event.target.nodeName == "IMG") { + element = event.target; + while (element && element != this._element) { + if (element.nodeName == "A") { + if (element != this.target) { + this.target = element; + this._needsReset = true; + if (this._isActive) { + this.needsDraw = true; + } else { + this._editor.showOverlay(this); + } + } + return true; + } + element = element.parentElement; + } + } + } + } + }, + + /** + Description TODO + @type {Function} + */ + editorTouchEnd: { + value: function(event) { + this.editorMouseUp(event); + } + }, + + /** + Description TODO + @type {Function} + */ + editorSelectionDidChange: { + value: function(range) { + var element; + + // Check if the caret is inside an anchor element + if (range && range.collapsed) { + element = range.commonAncestorContainer; + while (element && element != this._element) { + if (element.nodeName == "A") { + if (element != this.target) { + this.target = element; + this._needsReset = true; + if (this._isActive) { + this.needsDraw = true; + } else { + this._editor.showOverlay(this); + } + } + return true; + } + element = element.parentElement; + } + } + + if (this._editor.activeOverlay == this) { + this._editor.hideOverlay(); + } + + return false; + } + }, + + /** + Description TODO + @function + */ + didBecomeActive: { + value: function() { + this._isActive = true; + window.addEventListener("resize", this, false); + } + }, + + /** + Description TODO + @function + */ + didBecomeInactive: { + value: function() { + this._isActive = false; + window.removeEventListener("resize", this, false); + + //Reset the resizer internal + this.target = null; + this._needsReset = false; + } + }, + + /** + Description TODO + @function + */ + prepareForDraw: { + enumerable: false, + value: function() { + this._popupExtraWidth = this.element.offsetWidth; + } + }, + + /** + Description TODO + @function + */ + draw: { + enumerable: false, + value: function() { + var element = this.element, + target = this.target, + editorElement = this._editor.innerElement; + + if (this._needsReset) { + var offsetLeft, + offsetTop, + oh = editorElement.offsetHeight, + ow = editorElement.offsetWidth, + st = editorElement.scrollTop, + sl = editorElement.scrollLeft, + w = target.offsetWidth, + h = target.offsetHeight, + l, t, + left, leftWidth, right, rightWidth, + style = ""; + + var _findOffset = function(node) { + offsetTop = node.offsetTop; + offsetLeft = node.offsetLeft; + + while ((node = node.offsetParent) && node != editorElement) { + offsetTop += node.offsetTop; + offsetLeft += node.offsetLeft; + } + }; + _findOffset(target); + + l = offsetLeft; + t = offsetTop; + + // Should we display the popup on top or below the element? + if (t > 60 && t - st + h + 50 > oh) { + style = "bottom: " + (oh - t + 5) + "px;"; + } else { + style = "top: " + (t + h + 5 ) + "px;"; + } + + // Should we display the popup aligned on the left or right of the element? + left = sl; + right = sl + ow; + leftWidth = right - l; + rightWidth = l + w - left; + + if (leftWidth > rightWidth) { + //Let's align the popup to the left of the element or to the far left + if (leftWidth < 150) { + style += " left: " + (left + 5) + "px;"; + style += " max-width: " + (ow - 10 - this._popupExtraWidth) + "px;"; + } else { + style += " left: " + (left + l) + "px;"; + style += " max-width: " + (leftWidth - 5 - this._popupExtraWidth) + "px;"; + } + } else { + if (rightWidth < 150) { + style += " right: " + (left + 5) + "px;"; + style += " max-width: " + (ow - 10 - this._popupExtraWidth) + "px;"; + } else { + style += " right: " + (right - (left + l + w + 1)) + "px;"; + style += " max-width: " + (rightWidth - this._popupExtraWidth) + "px;"; + } + } + + // Position and size the popup + element.setAttribute("style", style); + + // Setup the anchor + this.link.href = target.href; + this.link.textContent = target.href; + + this._needsReset = false; + } + } + }, + + /** + Description TODO + @function + */ + handleResize: { + enumerable: false, + value: function() { + this._needsReset = true; + this.needsDraw = true; + } + } + +}); \ No newline at end of file diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.css b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.css new file mode 100644 index 00000000..3147fc67 --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.css @@ -0,0 +1,141 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + +.montage-resizer { + display: inline-block; +} + +.montage-resizer-container { + position: absolute; + border: 1px solid black; +} + +.montage-resizer-handle { + position: absolute; + width: 7px; + height: 7px; + border: 1px solid black; + -moz-border-radius: 4px; + border-radius: 4px; + background: white; +} + +.montage-resizer-handle:hover { + width: 9px; + height: 9px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +#montage-resizer-handle-n { + top: -4px; + left: 50%; + margin-left: -4px; + cursor: n-resize; +} + +#montage-resizer-handle-n:hover { + top: -5px; + margin-left: -5px; +} + +#montage-resizer-handle-ne { + top: -4px; + right: -4px; + cursor: ne-resize; +} + +#montage-resizer-handle-ne:hover { + top: -5px; + right: -5px; +} + +#montage-resizer-handle-e { + right: -4px; + top: 50%; + margin-top: -4px; + cursor: e-resize; +} + +#montage-resizer-handle-e:hover { + right: -5px; + margin-top: -5px; +} + +#montage-resizer-handle-se { + bottom: -4px; + right: -4px; + cursor: se-resize; +} + +#montage-resizer-handle-se:hover { + bottom: -5px; + right: -5px; +} + +#montage-resizer-handle-s { + bottom: -4px; + left: 50%; + margin-left: -4px; + cursor: s-resize; +} + +#montage-resizer-handle-s:hover { + bottom: -5px; + margin-left: -5px; +} + +#montage-resizer-handle-sw { + bottom: -4px; + left: -4px; + cursor: sw-resize; +} + +#montage-resizer-handle-sw:hover { + bottom: -5px; + left: -5px; +} + +#montage-resizer-handle-w { + left: -4px; + top: 50%; + margin-top: -4px; + cursor: w-resize; +} + +#montage-resizer-handle-w:hover { + left: -5px; + margin-top: -5px; +} + +#montage-resizer-handle-nw { + top: -4px; + left: -4px; + cursor: nw-resize; +} + +#montage-resizer-handle-nw:hover { + top: -5px; + left: -5px; +} + +.montage-resizer-image { + position: absolute; + display: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.5; +} + +.montage-editor-resizing .montage-resizer-image { + display: block; +} + +.montage-resizer-element::selection { + background: rgba(0,0,0,0); +} diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.html b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.html new file mode 100644 index 00000000..56f6cf19 --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.html @@ -0,0 +1,45 @@ + + + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.js b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.js new file mode 100644 index 00000000..c8a00ef8 --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.js @@ -0,0 +1,568 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module "montage/ui/rich-text-editor/overlays/rich-text-resizer.reel" + @requires montage/core/core + @requires montage/core/geometry/point + @requires montage/ui/component + @requires montage/ui/dom +*/ +var Montage = require("montage").Montage, + Component = require("ui/component").Component, + dom = require("ui/dom"), + Point = require("core/geometry/point").Point; + +/** + @class module:"montage/ui/rich-text-editor/overlays/rich-text-resizer.reel".RichTextResizer + @extends module:montage/ui/component.Component +*/ +exports.RichTextResizer = Montage.create(Component,/** @lends module:"montage/ui/rich-text-editor/overlays/rich-text-resizer.reel".RichTextResizer# */ { + + /** + Description TODO + @private + */ + _isActive: { + enumerable: false, + value: false + }, + /** + Description TODO + @private + */ + _editor: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + target: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _draggedElement: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _needsReset: { + enumerable: false, + value: false + }, + + /** + Description TODO + @type {Function} + */ + initWithEditor: { + value: function(editor) { + this._editor = editor; + } + }, + + /** + Description TODO + @type {Function} + */ + editorMouseDown: { + value: function(event) { + var target = event.target; + + if (this._isActive && target === this.element) { + event.preventDefault(); + event.stopPropagation(); + return true; + } + } + }, + + /** + Description TODO + @type {Function} + */ + editorTouchStart: { + value: function(event) { + this.editorMouseDown(event); + } + }, + + /** + Description TODO + @type {Function} + */ + editorMouseUp: { + value: function(event) { + var target = event.target, + previousTarget = this.target; + + // Ignore this call if we are curently capturing the pointer + if (this._observedPointer) { + return true; + } else { + if (target === this.element && this._editor.activeOverlay == this) { + this._editor.hideOverlay(); + // We need to stop the event propagation to prevent the selection to be reset + event.target = this.target; // Retarget the event + event.preventDefault(); + event.stopPropagation(); + } else if (target.tagName === "IMG") { + if (target !== previousTarget) { + if (previousTarget) { + previousTarget.classList.remove("montage-resizer-element"); + if (previousTarget.classList.length == 0) { + previousTarget.removeAttribute("class"); + } + } + this.target = target; + this._needsReset = true; + if (this._isActive) { + this.needsDraw = true; + } else { + this._ignoreNextSelectionchanged = true; + this._editor.showOverlay(this); + } + } + event.preventDefault(); + event.stopPropagation(); + return true; + } else if (this._editor.activeOverlay == this) { + this._editor.hideOverlay(); + } + } + + return false; + } + }, + + /** + Description TODO + @type {Function} + */ + editorTouchEnd: { + value: function(event) { + this.editorMouseUp(event); + } + }, + + /** + Description TODO + @type {Function} + */ + editorSelectionDidChange: { + value: function(range) { + if (this._ignoreNextSelectionchanged) { + this._ignoreNextSelectionchanged = false; + } else if (this._editor.activeOverlay == this) { + this._editor.hideOverlay(); + } + + return false; + } + }, + + /** + Description TODO + @function + */ + draw: { + enumerable: false, + value: function() { + var element = this.element, + target = this.target, + editorElement = this._editor.innerElement, + style; + + if (this._needsReset) { + var offsetLeft, + offsetTop, + _findOffset = function(node) { + offsetTop = node.offsetTop; + offsetLeft = node.offsetLeft; + + while ((node = node.offsetParent) && node != editorElement) { + offsetTop += node.offsetTop; + offsetLeft += node.offsetLeft; + } + }; + _findOffset(target); + + // Initialize the resizer + style = element.style; + + style.width = target.offsetWidth + "px"; + style.height = target.offsetHeight + "px"; + style.top = offsetTop + "px";; + style.left = offsetLeft + "px"; + + this._editor.innerElement.classList.remove("montage-editor-resizing"); + target.classList.add("montage-resizer-element"); + + // Setup the image + this.image.src = target.src; + this.image.title = target.title; + this.image.alt = target.alt; + + // Select the resizedElement + this._selectElement(target); + + this._needsReset = false; + } + + if (this._draggedElement) { + // Resize the resizer + var zero = Point.create().init(0, 0), + framePosition = dom.convertPointFromNodeToPage(element, zero), + cursor = this._cursorPosition, + direction = this._draggedElement.id.substring("montage-resizer-handle-".length), + info = this._resizerFrameInfo, + ratio = info.ratio, + height = parseFloat(element.style.height, 10), + width = parseFloat(element.style.width, 10), + top = parseFloat(element.style.top, 10), + left = parseFloat(element.style.left, 10), + minSize = 15; + + this._editor.innerElement.classList.add("montage-editor-resizing"); + + if (direction == "n") { + height += framePosition.y - cursor.y; + top = info.top - (height - info.height); + } else if (direction == "ne") { + height += framePosition.y - cursor.y; + width = Math.round(height * ratio); + if (cursor.x > (framePosition.x + width)) { + width = cursor.x - framePosition.x; + height = Math.round(width / ratio); + } + top = info.top - (height - info.height); + } else if (direction == "e") { + width = cursor.x - framePosition.x; + } else if (direction == "se") { + height = cursor.y - framePosition.y; + width = Math.round(height * ratio); + if (cursor.x > (framePosition.x + width)) { + width = cursor.x - framePosition.x; + height = Math.round(width / ratio); + } + } else if (direction == "s") { + height = cursor.y - framePosition.y; + } else if (direction == "sw") { + height = cursor.y - framePosition.y; + width = Math.round(height * ratio); + if (cursor.x <= framePosition.x - width + element.clientWidth) { + width = element.clientWidth + framePosition.x - cursor.x; + height = Math.round(width / ratio); + } + left = info.left - (width - info.width); + } else if (direction == "w") { + width += framePosition.x - cursor.x; + left = info.left - (width - info.width); + } else if (direction == "nw") { + height += framePosition.y - cursor.y; + width = Math.round(height * ratio); + if (cursor.x <= framePosition.x - width + element.clientWidth) { + width = element.clientWidth + framePosition.x - cursor.x; + height = Math.round(width / ratio); + } + top = info.top - (height - info.height); + left = info.left - (width - info.width); + } + + //set the frame's new height and width + if (height > minSize && width > minSize) { + element.style.height = height + "px"; + element.style.width = width + "px"; + element.style.top = top + "px"; + element.style.left = left + "px"; + } + } + + if (this._finalizeDrag) { + width = element.clientWidth; + height = element.clientHeight; + + this._editor.innerElement.classList.remove("montage-editor-resizing"); + target.classList.remove("montage-resizer-element"); + if (target.classList.length == 0) { + target.removeAttribute("class"); + } + + // Select the resizedElement (just in case) + this._selectElement(target); + + // Take the element offline to modify it + var div = document.createElement("div"), + offlineElement, + savedID; + div.appendChild(target.cloneNode(true)); + offlineElement = div.firstChild; + + // Resize the element now that it's offline + offlineElement.width = width; + offlineElement.height = height; + offlineElement.style.removeProperty("width"); + offlineElement.style.removeProperty("height"); + + savedID = offlineElement.id; + offlineElement.id = "montage-editor-resized-image"; + + // Inject the resized element into the contentEditable using execCommand in order to be in the browser undo queue + this._editor.execCommand("inserthtml", false, div.innerHTML, "Resizing Image"); + target = document.getElementById(offlineElement.id); + if (target) { + if (savedID !== undefined && savedID !== "") { + target.id = savedID; + } else { + target.removeAttribute("id"); + } + + // Add back the resizer + this.target = target; + this._needsReset = true; + this.needsDraw = true; + } + + this._finalizeDrag = false; + this._ignoreNextSelectionchanged = true; + } + } + }, + + /** + Description TODO + @function + */ + didBecomeActive: { + value: function() { + this._isActive = true; + this.element.addEventListener(window.Touch ? "touchstart" : "mousedown", this, false); + window.addEventListener("resize", this, false); + } + }, + + /** + Description TODO + @function + */ + didBecomeInactive: { + value: function() { + var target = this.target; + + this._isActive = false; + + this.element.removeEventListener(window.Touch ? "touchstart" : "mousedown", this, false); + window.removeEventListener("resize", this, false); + + if (this._draggedElement) { + if (window.Touch) { + document.removeEventListener("touchmove", this); + document.removeEventListener("touchend", this); + } else { + document.removeEventListener("mousemove", this); + document.removeEventListener("mouseup", this); + } + this._releaseInterest(); + } + + if (target) { + // Let's do some extra cleanup + target.classList.remove("montage-resizer-element"); + if (target.classList.length == 0) { + target.removeAttribute("class"); + } + this._editor.markDirty(); + } + + //Reset the resizer internal + this.target = null; + this._needsReset = false; + this._draggedElement = null; + this._finalizeDrag = false; + } + }, + + /** + Description TODO + @function + */ + handleResize: { + enumerable: false, + value: function() { + this._needsReset = true; + this.needsDraw = true; + } + }, + + /** + Description TODO + @function + */ + handleMousedown: { + value: function(event) { + var target = event.target, + element = this.element; + + if (target.classList.contains("montage-resizer-handle")) { + if (window.Touch) { + this._observePointer(target.id); + document.addEventListener("touchmove", this); + document.addEventListener("touchend", this); + } else { + this._observePointer("mouse"); + document.addEventListener("mousemove", this); + document.addEventListener("mouseup", this); + } + + this._resizerFrameInfo = { + width: element.clientWidth, + height: element.clientHeight, + left: parseInt(element.style.left, 10), + top: parseInt(element.style.top, 10), + ratio: element.clientWidth / element.clientHeight + }; + this._cursorPosition = {x:event.pageX, y:event.pageY}; + this._draggedElement = target; + + event.preventDefault(); + event.stopPropagation(); + } + } + }, + + /** + Description TODO + @function + */ + handleTouchstart: { + value: function(event) { + this.handleMousedown(event); + } + }, + + /** + Description TODO + @function + */ + handleMousemove: { + value: function(event) { + + this._cursorPosition = {x:event.pageX, y:event.pageY}; + this.needsDraw = true; + + event.preventDefault(); + event.stopPropagation(); + } + }, + + /** + Description TODO + @function + */ + handleTouchmove: { + value: function(event) { + this.handleMousemove(event); + } + }, + + /** + Description TODO + @function + */ + handleMouseup: { + value: function(event) { + if (this._draggedElement) { + if (window.Touch) { + document.removeEventListener("touchmove", this); + document.removeEventListener("touchend", this); + } else { + document.removeEventListener("mousemove", this); + document.removeEventListener("mouseup", this); + } + + this._draggedElement = null; + + this._finalizeDrag = true; + this.needsDraw = true; + + this._releaseInterest(); + + event.preventDefault(); + event.stopPropagation(); + } + } + }, + + /** + Description TODO + @function + */ + handleTouchend: { + value: function(event) { + this.handleMouseup(event); + } + }, + + /** + Description TODO + @function + @param {String} pointer TODO + @param {Component} demandingComponent TODO + @returns {Boolean} false + */ + surrenderPointer: { + value: function(pointer, demandingComponent) { + return false; + } + }, + + /** + Description TODO + @private + */ + _observePointer: { + value: function(pointer) { + this.eventManager.claimPointer(pointer, this); + this._observedPointer = pointer; + } + }, + + /** + Description TODO + @private + */ + _releaseInterest: { + value: function() { + this.eventManager.forfeitPointer(this._observedPointer, this); + this._observedPointer = null; + } + }, + + /** + Description TODO + @private + */ + _selectElement: { + value: function(element) { + var offset, + range; + + this._ignoreNextSelectionchanged = true; + this._editor.selectElement(element); + } + } +}); \ No newline at end of file diff --git a/node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor-base.js b/node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor-base.js new file mode 100644 index 00000000..e92da424 --- /dev/null +++ b/node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor-base.js @@ -0,0 +1,1706 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module "montage/ui/rich-text-editor.reel" + @requires montage/core/core +*/ +var Montage = require("montage").Montage, + Component = require("ui/component").Component, + MutableEvent = require("core/event/mutable-event").MutableEvent, + Sanitizer = require("./rich-text-sanitizer").Sanitizer, + RichTextLinkPopup = require("../overlays/rich-text-linkpopup.reel").RichTextLinkPopup, + RichTextResizer = require("../overlays/rich-text-resizer.reel").RichTextResizer, + defaultEventManager = require("core/event/event-manager").defaultEventManager, + defaultUndoManager = require("core/undo-manager").defaultUndoManager; + +/** + @class module:"montage/ui/rich-text-editor.reel".RichTextEditorBase + @extends module:montage/ui/component.Component +*/ +exports.RichTextEditorBase = Montage.create(Component,/** @lends module:"montage/ui/rich-text-editor.reel".RichTextEditor# */ { + + /** + Description TODO + @private + */ + _overlays: { + enumerable: false, + value: undefined + }, + + /** + Description TODO + @private + */ + _overlaySlot: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _activeOverlay: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _innerElement: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _undoManager: { + enumerable: false, + value: undefined + }, + + /** + Description TODO + @private + */ + _isTyping: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _startTyping: { + enumerable: false, + value: function() { + if (this._doingUndoRedo) { + this._isTyping = false; + return; + } else if (!this._isTyping) { + this._isTyping = true; + if (this.undoManager) { + this.undoManager.add("Typing", this._undo, this, "Typing", this._innerElement); + } + } + } + }, + + /** + Description TODO + @private + */ + _stopTyping: { + enumerable: false, + value: function() { + if (this._isTyping) { + this._isTyping = false; + } + } + }, + + /** + Description TODO + @private + */ + _hasSelectionChangeEvent: { + enumerable: false, + value: null // Need to be preset to null, will be set to true or false later on + }, + + /** + Description TODO + @private + */ + _uniqueId: { + enumerable: false, + value: Math.floor(Math.random() * 1000) + "-" + Math.floor(Math.random() * 1000) + }, + + /** + Description TODO + @private + */ + _contentInitialized: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _needsAssignOriginalContent: { + enumerable: false, + value: true + }, + + /** + Description TODO + @private + */ + _needsAssingValue: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _setCaretAtEndOfContent: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _selectionChangeTimer: { + enumerable: false, + value: null + }, + + /** + Description TODO + @private + */ + _hasFocus: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _needsFocus: { + value: false + }, + + /** + Description TODO + @private + */ + _isActiveElement: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _readOnly: { + enumerable: false, + value: false + }, + + /** + Description TODO + @private + */ + _value: { + enumerable: false, + value: "" + }, + + /** + Description TODO + @private + */ + _textValue: { + enumerable: false, + value: "" + }, + + /** + Description TODO + @type {} + */ + delegate: { + enumerable: true, + value: null + }, + + /** + Description TODO + @private + */ + _sanitizer: { + enumerable: false, + value: undefined + }, + + /** + Description TODO + @private + */ + _allowDrop: { + enumerable: false, + value: true + }, + + // Commands Helpers + _getState: { + value: function(property, command) { + var state; + + if (this._innerElement == document.activeElement) { + state = document.queryCommandValue(command); + // Convert string to boolean + if (state == "true") { + state = true; + } if (state == "false") { + state = false; + } + return state; + } else { + return this["_" + property]; + } + } + }, + + _genericCommandGetter : { + value: function(property, command) { + var propertyName = "_" + property; + this[propertyName] = this._getState(property, command); + return this[propertyName]; + } + }, + + _genericCommandSetter : { + value: function(property, command, value) { + var state = this._getState(property, command); // Make sure the state is up-to-date + if (state !== value) { + this.doAction(command, typeof value == "boolean" ? false : value); + } + } + }, + + // Edit Actions & Properties + /** + Description TODO + @private + */ + _bold: { value: false }, + + /** + Description TODO + @private + */ + _underline: { value: false }, + + /** + Description TODO + @private + */ + _italic: { value: false }, + + /** + Description TODO + @private + */ + _strikeThrough: { value: false }, + + /** + Description TODO + @private + */ + _baselineShiftGetState: { + enumerable: false, + value: function() { + if (this._innerElement == document.activeElement) { + if (this._getState("baselineShift", "subscript")) { + return "subscript" + } else if (this._getState("baselineShift", "superscript")) { + return "superscript" + } else { + return "baseline"; // default + } + } else { + return this._baselineShift; + } + } + }, + + /** + Description TODO + @private + */ + _baselineShift: { value: "baseline" }, + + /** + Description TODO + @private + */ + _listStyleGetState: { + enumerable: false, + value: function() { + if (this._innerElement == document.activeElement) { + if (this._getState("listStyle", "insertorderedlist")) { + return "ordered" + } else if (this._getState("listStyle", "insertunorderedlist")) { + return "unordered" + } else { + return "none"; // default + } + } else { + return this._listStyle; + } + } + }, + /** + Description TODO + @private + */ + _listStyle: { value: "none" }, + + /** + Description TODO + @private + */ + _justifyGetState: { + enumerable: false, + value: function() { + if (this._innerElement == document.activeElement) { + if (this._getState("justify", "justifyleft")) { + return "left" + } else if (this._getState("justify", "justifycenter")) { + return "center" + } else if (this._getState("justify", "justifyright")) { + return "right" + } else if (this._getState("justify", "justifyfull")) { + return "full" + } else { + return "left"; // default + } + } else { + return this._justify; + } + } + }, + + /** + Description TODO + @private + */ + _justify: { value: "left" }, + + /** + Description TODO + @private + */ + _fontNameGetState: { + enumerable: false, + value: function() { + this._fontName = this._getState("fontName", "fontname"); + if (this._fontName) { + this._fontName = this._fontName.replace(/\"|\'/g, ""); + } + + return this._fontName; + } + }, + /** + Description TODO + @private + */ + _fontName: { value: "" }, + + /** + Description TODO + @private + */ + _fontSize: { value: 0 }, + + /** + Description TODO + @private + */ + _backColor: { value: "" }, + + /** + Description TODO + @private + */ + _foreColor: { value: "" }, + + + /** + Description TODO + @type {Function} + */ + _updateStates: { + enumerable: true, + value: function() { + var commands = [{property: "bold"}, + {property: "underline"}, + {property: "italic"}, + {property: "strikeThrough"}, + {property: "baselineShift", method: this._baselineShiftGetState}, + {property: "justify", method: this._justifyGetState}, + {property: "listStyle", method: this._listStyleGetState}, + {property: "fontName", method: this._fontNameGetState}, + {property: "fontSize"}, + {property: "backColor"}, + {property: "foreColor"} + ], + nbrCommands = commands.length, + command, + commandName, + propertyName, + state, + prevState, + method, + i; + + if (this._innerElement == document.activeElement) { + for (i = 0; i < nbrCommands; i ++) { + command = commands[i]; + + if (typeof command == "object") { + propertyName = command.property; + commandName = command.name || propertyName.toLowerCase(); + method = command.method || this._getState; + } else { + continue; + } + + if (defaultEventManager.registeredEventListenersForEventType_onTarget_("change@" + propertyName, this)) { + prevState = this["_" + propertyName]; + state = method.call(this, propertyName, commandName); + if (state !== prevState) { + this["_" + propertyName] = state; + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue(propertyName , prevState).withPlusValue(state)); + } + } + } + + } + } + }, + + // Component Callbacks + + /** + Description TODO + @function + */ + prepareForDraw: { + enumerable: false, + value: function() { + var el = this.element; + + el.classList.add('montage-editor-container'); + + el.addEventListener("focus", this); + el.addEventListener("dragstart", this, false); + el.addEventListener("dragenter", this, false); + el.addEventListener("dragover", this, false); + el.addEventListener("drop", this, false); + el.addEventListener("dragend", this, false); + + // Setup the sanitizer if not specified + if (this._sanitizer === undefined) { + this._sanitizer = Sanitizer.create(); + } + + // Setup the undoManager if not specified + if (this._undoManager === undefined) { + this._undoManager = defaultUndoManager; + } + + // Initialize the overlays + if (this._overlays === undefined) { + // Install the default overlays + this._overlays = [RichTextResizer.create(), RichTextLinkPopup.create()]; + } + this._callOverlays("initWithEditor", this, true); + } + }, + + /** + Description TODO + @function + */ + draw: { + enumerable: false, + value: function() { + var editorElement = this.element, + editorInnerElement, + contents, + content, + contentChanged, + prevValue, + i; + + if (this._needsAssingValue || this._needsAssignOriginalContent) { + editorInnerElement = this._innerElement = editorElement.querySelector(".montage-editor"); + + if (this._contentInitialized) { + // if the content has been already initialized, we need replace it by a clone of itself + // in order to reset the browser undo stack + editorElement.replaceChild(editorInnerElement.cloneNode(true), editorInnerElement); + editorInnerElement = this._innerElement = editorElement.querySelector(".montage-editor"); + + //JFD TODO: Need to clear entries in the Montage undoManager queue + } + + editorInnerElement.setAttribute("contenteditable", (this._readOnly ? "false" : "true")); + editorInnerElement.classList.add("editor-" + this._uniqueId); + editorInnerElement.innerHTML = ""; + + if (this._needsAssingValue) { + // Set the contentEditable value + if (this._value && !this._dirtyValue) { + editorInnerElement.innerHTML = this._value; + // Since this property affects the textValue, we need to fire a change event for it as well + if (defaultEventManager.registeredEventListenersForEventType_onTarget_("change@textValue", this)) { + prevValue = this._textValue; + if (this.textValue !== prevValue) { + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("textValue" , prevValue).withPlusValue(text.value)); + } + } + } else if (this._textValue && !this._dirtyTextValue) { + if (editorInnerElement.innerText) { + editorInnerElement.innerText = this._textValue; + } else { + editorInnerElement.textContent = this._textValue; + } + // Since this property affects the value, we need to fire a change event for it as well + if (defaultEventManager.registeredEventListenersForEventType_onTarget_("change@value", this)) { + prevValue = this._value; + if (this.value !== prevValue) { + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("value" , prevValue).withPlusValue(this.value)); + } + } + } + } else if (this._needsAssignOriginalContent) { + contents = this.originalContent; + contentChanged = false; + if (contents instanceof Element) { + editorInnerElement.appendChild(contents); + contentChanged = true; + } else { + for (i = 0; (content = contents[i]); i++) { + editorInnerElement.appendChild(content); + contentChanged = true; + } + } + if (contentChanged) { + if (defaultEventManager.registeredEventListenersForEventType_onTarget_("change@value", this)) { + prevValue = this._value; + if (this.value !== prevValue) { + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("value" , prevValue).withPlusValue(this.value)); + } + } + if (defaultEventManager.registeredEventListenersForEventType_onTarget_("change@textValue", this)) { + prevValue = this._textValue; + if (this.textValue !== prevValue) { + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("textValue" , prevValue).withPlusValue(text.value)); + } + } + + // Clear the cached value in order to force an editorChange event + this._dirtyValue = true; + this._dirtyTextValue = true; + } + } + + this._adjustPadding(); + this.markDirty(); + + this._needsAssingValue = false; + this._needsAssignOriginalContent = false; + this._contentInitialized = true; + + this._setCaretAtEndOfContent = true; + if (this.hasFocus) { + // Call focus to move caret to end of document + this.focus(); + } + + } else { + editorInnerElement = this._innerElement; + } + + if (this._readOnly) { + editorInnerElement.setAttribute("contentEditable", "false"); + editorElement.classList.add("readonly") + } else { + editorInnerElement.setAttribute("contentEditable", "true"); + editorElement.classList.remove("readonly") + } + } + }, + + /** + Description TODO + @function + */ + didDraw: { + value: function() { + if (this._needsFocus) { + this._innerElement.focus(); + if(document.activeElement == this._innerElement) { + this._needsFocus = false; + } else { + // Make sure the element is visible before trying again to set the focus + var style = window.getComputedStyle(this.element); + if (style.getPropertyValue("visibility") == "hidden" || style.getPropertyValue("display") == "none") { + this._needsFocus = false; + } else { + this.needsDraw = true; + } + } + } + } + }, + + /** + Description TODO + @function + */ + slotDidSwitchContent: { + enumerable: false, + value: function(substitution, nodeShown, componentShown, nodeHidden, componentHidden) { + if(componentHidden && typeof componentHidden.didBecomeInactive === 'function') { + componentHidden.didBecomeInactive(); + } + if(componentShown && typeof componentShown.didBecomeActive === 'function') { + componentShown.didBecomeActive(); + } + } + }, + + /** + Description TODO + @function + */ + _adjustPadding: { + enumerable: false, + value: function() { + var el = this._innerElement, + minLeft = 0, + minTop = 0; + + var walkTree = function(node, parentLeft, parentTop) { + var nodes = node ? node.childNodes : [], + nbrNodes = nodes.length, + i, + offsetLeft = node.offsetLeft, + offsetTop = node.offsetTop; + + if (node.offsetParent) { + offsetLeft += parentLeft; + offsetTop += parentTop; + } + if (minLeft > offsetLeft) { + minLeft = offsetLeft; + } + if (minTop > offsetTop) { + minTop = offsetTop; + } + + for (i = 0; i < nbrNodes; i ++) { + walkTree(nodes[i], offsetLeft, offsetTop) + } + }; + walkTree(el, el.offsetLeft, el.offsetTop); + + var computedStyle = document.defaultView.getComputedStyle(el), + paddingLeft = computedStyle.paddingLeft, + paddingTop = computedStyle.paddingTop; + + if (paddingLeft.match(/%$/)) { + paddingLeft = parseInt(paddingLeft, 10) * el.clientWidth; + } else { + paddingLeft = parseInt(paddingLeft, 10); + } + if (paddingTop.match(/%$/)) { + paddingTop = parseInt(paddingTop, 10) * el.clientHeight; + } else { + paddingTop = parseInt(paddingTop, 10); + } + + if (minLeft < 0) { + el.style.paddingLeft = (-minLeft - paddingLeft) + "px"; + } + if (minTop < 0) { + el.style.paddingTop = (-minTop - paddingTop) + "px"; + } + } + }, + + // Event handlers + // Event handlers + /** + Description TODO + @function + */ + handleFocus: { + enumerable: false, + value: function() { + var thisRef = this, + el = this.element, + content = this._innerElement, + isActive, + savedRange, + timer; + + this._hasFocus = true; + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("hasFocus" , false).withPlusValue(true)); + isActive = (content && content === document.activeElement); + if (isActive != this._isActiveElement) { + this._isActiveElement = isActive; + this.dispatchEvent(MutableEvent.changeEventForKeyAndValue("isActiveElement" , false).withPlusValue(true)); + } + + if (this._setCaretAtEndOfContent) { + var node = this._lastInnerNode(), + range, + length, + leafNodes = ["#text", "BR", "IMG"]; + + // Select the last inner node + if (node) { + if (leafNodes.indexOf(node.nodeName) !== -1) { + node = node.parentNode; + } + range = document.createRange(); + length = node.childNodes ? node.childNodes.length : 0; + range.setStart(node, length); + range.setEnd(node, length); + this._selectedRange = range; + } + + // Scroll the content to make sure the caret is visible, but only only if the focus wasn't the result of a user click/touch + savedRange = this._selectedRange; + timer = setInterval(function() { + if (thisRef._equalRange(thisRef._selectedRange, savedRange) && + content.scrollTop + content.offsetHeight != content.scrollHeight) { + content.scrollTop = content.scrollHeight - content.offsetHeight; + } + }, 10); + + setTimeout(function(){clearInterval(timer)}, 1000); + + this._setCaretAtEndOfContent = false; + } + + el.addEventListener("blur", this); + el.addEventListener("input", this); + el.addEventListener("keydown", this); + el.addEventListener("keypress", this); + el.addEventListener("cut", this); + el.addEventListener("paste", this); + el.addEventListener(window.Touch ? "touchstart" : "mousedown", this); + document.addEventListener(window.Touch ? "touchend" : "mouseup", this); + + document.addEventListener("selectionchange", this, false); + // Check if the browser does not supports the DOM event selec