123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- // Copyright 2007 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview A utility class for managing editable links.
- *
- * @author nicksantos@google.com (Nick Santos)
- */
- goog.provide('goog.editor.Link');
- goog.require('goog.array');
- goog.require('goog.dom');
- goog.require('goog.dom.NodeType');
- goog.require('goog.dom.Range');
- goog.require('goog.dom.TagName');
- goog.require('goog.editor.BrowserFeature');
- goog.require('goog.editor.Command');
- goog.require('goog.editor.Field');
- goog.require('goog.editor.node');
- goog.require('goog.editor.range');
- goog.require('goog.string');
- goog.require('goog.string.Unicode');
- goog.require('goog.uri.utils');
- goog.require('goog.uri.utils.ComponentIndex');
- /**
- * Wrap an editable link.
- * @param {HTMLAnchorElement} anchor The anchor element.
- * @param {boolean} isNew Whether this is a new link.
- * @constructor
- * @final
- */
- goog.editor.Link = function(anchor, isNew) {
- /**
- * The link DOM element.
- * @type {HTMLAnchorElement}
- * @private
- */
- this.anchor_ = anchor;
- /**
- * Whether this link represents a link just added to the document.
- * @type {boolean}
- * @private
- */
- this.isNew_ = isNew;
- /**
- * Any extra anchors created by the browser from a selection in the same
- * operation that created the primary link
- * @type {!Array<HTMLAnchorElement>}
- * @private
- */
- this.extraAnchors_ = [];
- };
- /**
- * @return {HTMLAnchorElement} The anchor element.
- */
- goog.editor.Link.prototype.getAnchor = function() {
- return this.anchor_;
- };
- /**
- * @return {!Array<HTMLAnchorElement>} The extra anchor elements, if any,
- * created by the browser from a selection.
- */
- goog.editor.Link.prototype.getExtraAnchors = function() {
- return this.extraAnchors_;
- };
- /**
- * @return {string} The inner text for the anchor.
- */
- goog.editor.Link.prototype.getCurrentText = function() {
- if (!this.currentText_) {
- var anchor = this.getAnchor();
- var leaf = goog.editor.node.getLeftMostLeaf(anchor);
- if (leaf.tagName && leaf.tagName == goog.dom.TagName.IMG) {
- this.currentText_ = leaf.getAttribute('alt');
- } else {
- this.currentText_ = goog.dom.getRawTextContent(this.getAnchor());
- }
- }
- return this.currentText_;
- };
- /**
- * @return {boolean} Whether the link is new.
- */
- goog.editor.Link.prototype.isNew = function() {
- return this.isNew_;
- };
- /**
- * Set the url without affecting the isNew() status of the link.
- * @param {string} url A URL.
- */
- goog.editor.Link.prototype.initializeUrl = function(url) {
- this.getAnchor().href = url;
- };
- /**
- * Removes the link, leaving its contents in the document. Note that this
- * object will no longer be usable/useful after this call.
- */
- goog.editor.Link.prototype.removeLink = function() {
- goog.dom.flattenElement(this.anchor_);
- this.anchor_ = null;
- while (this.extraAnchors_.length) {
- goog.dom.flattenElement(/** @type {Element} */ (this.extraAnchors_.pop()));
- }
- };
- /**
- * Change the link.
- * @param {string} newText New text for the link. If the link contains all its
- * text in one descendent, newText will only replace the text in that
- * one node. Otherwise, we'll change the innerHTML of the whole
- * link to newText.
- * @param {string} newUrl A new URL.
- */
- goog.editor.Link.prototype.setTextAndUrl = function(newText, newUrl) {
- var anchor = this.getAnchor();
- anchor.href = newUrl;
- // If the text did not change, don't update link text.
- var currentText = this.getCurrentText();
- if (newText != currentText) {
- var leaf = goog.editor.node.getLeftMostLeaf(anchor);
- if (leaf.tagName && leaf.tagName == goog.dom.TagName.IMG) {
- leaf.setAttribute('alt', newText ? newText : '');
- } else {
- if (leaf.nodeType == goog.dom.NodeType.TEXT) {
- leaf = leaf.parentNode;
- }
- if (goog.dom.getRawTextContent(leaf) != currentText) {
- leaf = anchor;
- }
- goog.dom.removeChildren(leaf);
- var domHelper = goog.dom.getDomHelper(leaf);
- goog.dom.appendChild(leaf, domHelper.createTextNode(newText));
- }
- // The text changed, so force getCurrentText to recompute.
- this.currentText_ = null;
- }
- this.isNew_ = false;
- };
- /**
- * Places the cursor to the right of the anchor.
- * Note that this is different from goog.editor.range's placeCursorNextTo
- * in that it specifically handles the placement of a cursor in browsers
- * that trap you in links, by adding a space when necessary and placing the
- * cursor after that space.
- */
- goog.editor.Link.prototype.placeCursorRightOf = function() {
- var anchor = this.getAnchor();
- // If the browser gets stuck in a link if we place the cursor next to it,
- // we'll place the cursor after a space instead.
- if (goog.editor.BrowserFeature.GETS_STUCK_IN_LINKS) {
- var spaceNode;
- var nextSibling = anchor.nextSibling;
- // Check if there is already a space after the link. Only handle the
- // simple case - the next node is a text node that starts with a space.
- if (nextSibling && nextSibling.nodeType == goog.dom.NodeType.TEXT &&
- (goog.string.startsWith(nextSibling.data, goog.string.Unicode.NBSP) ||
- goog.string.startsWith(nextSibling.data, ' '))) {
- spaceNode = nextSibling;
- } else {
- // If there isn't an obvious space to use, create one after the link.
- var dh = goog.dom.getDomHelper(anchor);
- spaceNode = dh.createTextNode(goog.string.Unicode.NBSP);
- goog.dom.insertSiblingAfter(spaceNode, anchor);
- }
- // Move the selection after the space.
- var range = goog.dom.Range.createCaret(spaceNode, 1);
- range.select();
- } else {
- goog.editor.range.placeCursorNextTo(anchor, false);
- }
- };
- /**
- * Updates the cursor position and link bubble for this link.
- * @param {goog.editor.Field} field The field in which the link is created.
- * @param {string} url The link url.
- * @private
- */
- goog.editor.Link.prototype.updateLinkDisplay_ = function(field, url) {
- this.initializeUrl(url);
- this.placeCursorRightOf();
- field.execCommand(goog.editor.Command.UPDATE_LINK_BUBBLE);
- };
- /**
- * @return {string?} The modified string for the link if the link
- * text appears to be a valid link. Returns null if this is not
- * a valid link address.
- */
- goog.editor.Link.prototype.getValidLinkFromText = function() {
- var text = goog.string.trim(this.getCurrentText());
- if (goog.editor.Link.isLikelyUrl(text)) {
- if (text.search(/:/) < 0) {
- return 'http://' + goog.string.trimLeft(text);
- }
- return text;
- } else if (goog.editor.Link.isLikelyEmailAddress(text)) {
- return 'mailto:' + text;
- }
- return null;
- };
- /**
- * After link creation, finish creating the link depending on the type
- * of link being created.
- * @param {goog.editor.Field} field The field where this link is being created.
- */
- goog.editor.Link.prototype.finishLinkCreation = function(field) {
- var linkFromText = this.getValidLinkFromText();
- if (linkFromText) {
- this.updateLinkDisplay_(field, linkFromText);
- } else {
- field.execCommand(goog.editor.Command.MODAL_LINK_EDITOR, this);
- }
- };
- /**
- * Initialize a new link.
- * @param {HTMLAnchorElement} anchor The anchor element.
- * @param {string} url The initial URL.
- * @param {string=} opt_target The target.
- * @param {Array<HTMLAnchorElement>=} opt_extraAnchors Extra anchors created
- * by the browser when parsing a selection.
- * @return {!goog.editor.Link} The link.
- */
- goog.editor.Link.createNewLink = function(
- anchor, url, opt_target, opt_extraAnchors) {
- var link = new goog.editor.Link(anchor, true);
- link.initializeUrl(url);
- if (opt_target) {
- anchor.target = opt_target;
- }
- if (opt_extraAnchors) {
- link.extraAnchors_ = opt_extraAnchors;
- }
- return link;
- };
- /**
- * Initialize a new link using text in anchor, or empty string if there is no
- * likely url in the anchor.
- * @param {HTMLAnchorElement} anchor The anchor element with likely url content.
- * @param {string=} opt_target The target.
- * @return {!goog.editor.Link} The link.
- */
- goog.editor.Link.createNewLinkFromText = function(anchor, opt_target) {
- var link = new goog.editor.Link(anchor, true);
- var text = link.getValidLinkFromText();
- link.initializeUrl(text ? text : '');
- if (opt_target) {
- anchor.target = opt_target;
- }
- return link;
- };
- /**
- * Returns true if str could be a URL, false otherwise
- *
- * Ex: TR_Util.isLikelyUrl_("http://www.google.com") == true
- * TR_Util.isLikelyUrl_("www.google.com") == true
- *
- * @param {string} str String to check if it looks like a URL.
- * @return {boolean} Whether str could be a URL.
- */
- goog.editor.Link.isLikelyUrl = function(str) {
- // Whitespace means this isn't a domain.
- if (/\s/.test(str)) {
- return false;
- }
- if (goog.editor.Link.isLikelyEmailAddress(str)) {
- return false;
- }
- // Add a scheme if the url doesn't have one - this helps the parser.
- var addedScheme = false;
- if (!/^[^:\/?#.]+:/.test(str)) {
- str = 'http://' + str;
- addedScheme = true;
- }
- // Parse the domain.
- var parts = goog.uri.utils.split(str);
- // Relax the rules for special schemes.
- var scheme = parts[goog.uri.utils.ComponentIndex.SCHEME];
- if (goog.array.indexOf(['mailto', 'aim'], scheme) != -1) {
- return true;
- }
- // Require domains to contain a '.', unless the domain is fully qualified and
- // forbids domains from containing invalid characters.
- var domain = parts[goog.uri.utils.ComponentIndex.DOMAIN];
- if (!domain || (addedScheme && domain.indexOf('.') == -1) ||
- (/[^\w\d\-\u0100-\uffff.%]/.test(domain))) {
- return false;
- }
- // Require http and ftp paths to start with '/'.
- var path = parts[goog.uri.utils.ComponentIndex.PATH];
- return !path || path.indexOf('/') == 0;
- };
- /**
- * Regular expression that matches strings that could be an email address.
- * @type {RegExp}
- * @private
- */
- goog.editor.Link.LIKELY_EMAIL_ADDRESS_ = new RegExp(
- '^' + // Test from start of string
- '[\\w-]+(\\.[\\w-]+)*' + // Dot-delimited alphanumerics and dashes
- // (name)
- '\\@' + // @
- '([\\w-]+\\.)+' + // Alphanumerics, dashes and dots (domain)
- '(\\d+|\\w\\w+)$', // Domain ends in at least one number or 2 letters
- 'i');
- /**
- * Returns true if str could be an email address, false otherwise
- *
- * Ex: goog.editor.Link.isLikelyEmailAddress_("some word") == false
- * goog.editor.Link.isLikelyEmailAddress_("foo@foo.com") == true
- *
- * @param {string} str String to test for being email address.
- * @return {boolean} Whether "str" looks like an email address.
- */
- goog.editor.Link.isLikelyEmailAddress = function(str) {
- return goog.editor.Link.LIKELY_EMAIL_ADDRESS_.test(str);
- };
- /**
- * Determines whether or not a url is an email link.
- * @param {string} url A url.
- * @return {boolean} Whether the url is a mailto link.
- */
- goog.editor.Link.isMailto = function(url) {
- return !!url && goog.string.startsWith(url, 'mailto:');
- };
|