/src/prime/lib/closure-library/goog/editor/plugins/linkbubble.js
JavaScript | 458 lines | 190 code | 81 blank | 187 comment | 21 complexity | 13d7776c20bfc124e1ccf6258e196eef MD5 | raw file
- // Copyright 2008 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 Base class for bubble plugins.
- *
- */
- goog.provide('goog.editor.plugins.LinkBubble');
- goog.provide('goog.editor.plugins.LinkBubble.Action');
- goog.require('goog.array');
- goog.require('goog.dom');
- goog.require('goog.editor.BrowserFeature');
- goog.require('goog.editor.Command');
- goog.require('goog.editor.Link');
- goog.require('goog.editor.plugins.AbstractBubblePlugin');
- goog.require('goog.editor.range');
- goog.require('goog.string');
- goog.require('goog.style');
- goog.require('goog.ui.editor.messages');
- goog.require('goog.window');
- /**
- * Property bubble plugin for links.
- * @param {...!goog.editor.plugins.LinkBubble.Action} var_args List of
- * extra actions supported by the bubble.
- * @constructor
- * @extends {goog.editor.plugins.AbstractBubblePlugin}
- */
- goog.editor.plugins.LinkBubble = function(var_args) {
- goog.base(this);
- /**
- * List of extra actions supported by the bubble.
- * @type {Array.<!goog.editor.plugins.LinkBubble.Action>}
- * @private
- */
- this.extraActions_ = goog.array.toArray(arguments);
- /**
- * List of spans corresponding to the extra actions.
- * @type {Array.<!Element>}
- * @private
- */
- this.actionSpans_ = [];
- };
- goog.inherits(goog.editor.plugins.LinkBubble,
- goog.editor.plugins.AbstractBubblePlugin);
- /**
- * Element id for the link text.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.LINK_TEXT_ID_ = 'tr_link-text';
- /**
- * Element id for the test link span.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_ = 'tr_test-link-span';
- /**
- * Element id for the test link.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.TEST_LINK_ID_ = 'tr_test-link';
- /**
- * Element id for the change link span.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.CHANGE_LINK_SPAN_ID_ = 'tr_change-link-span';
- /**
- * Element id for the link.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_ = 'tr_change-link';
- /**
- * Element id for the delete link span.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.DELETE_LINK_SPAN_ID_ = 'tr_delete-link-span';
- /**
- * Element id for the delete link.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.DELETE_LINK_ID_ = 'tr_delete-link';
- /**
- * Element id for the link bubble wrapper div.
- * type {string}
- * @private
- */
- goog.editor.plugins.LinkBubble.LINK_DIV_ID_ = 'tr_link-div';
- /**
- * @desc Text label for link that lets the user click it to see where the link
- * this bubble is for point to.
- */
- var MSG_LINK_BUBBLE_TEST_LINK = goog.getMsg('Go to link: ');
- /**
- * @desc Label that pops up a dialog to change the link.
- */
- var MSG_LINK_BUBBLE_CHANGE = goog.getMsg('Change');
- /**
- * @desc Label that allow the user to remove this link.
- */
- var MSG_LINK_BUBBLE_REMOVE = goog.getMsg('Remove');
- /**
- * Whether to stop leaking the page's url via the referrer header when the
- * link text link is clicked.
- * @type {boolean}
- * @private
- */
- goog.editor.plugins.LinkBubble.prototype.stopReferrerLeaks_ = false;
- /**
- * Tells the plugin to stop leaking the page's url via the referrer header when
- * the link text link is clicked. When the user clicks on a link, the
- * browser makes a request for the link url, passing the url of the current page
- * in the request headers. If the user wants the current url to be kept secret
- * (e.g. an unpublished document), the owner of the url that was clicked will
- * see the secret url in the request headers, and it will no longer be a secret.
- * Calling this method will not send a referrer header in the request, just as
- * if the user had opened a blank window and typed the url in themselves.
- */
- goog.editor.plugins.LinkBubble.prototype.stopReferrerLeaks = function() {
- // TODO(user): Right now only 2 plugins have this API to stop
- // referrer leaks. If more plugins need to do this, come up with a way to
- // enable the functionality in all plugins at once.
- this.stopReferrerLeaks_ = true;
- };
- /** @override */
- goog.editor.plugins.LinkBubble.prototype.getTrogClassId = function() {
- return 'LinkBubble';
- };
- /** @override */
- goog.editor.plugins.LinkBubble.prototype.getBubbleTargetFromSelection =
- function(selectedElement) {
- var bubbleTarget = goog.dom.getAncestorByTagNameAndClass(selectedElement,
- goog.dom.TagName.A);
- if (!bubbleTarget) {
- // See if the selection is touching the right side of a link, and if so,
- // show a bubble for that link. The check for "touching" is very brittle,
- // and currently only guarantees that it will pop up a bubble at the
- // position the cursor is placed at after the link dialog is closed.
- // NOTE(robbyw): This assumes this method is always called with
- // selected element = range.getContainerElement(). Right now this is true,
- // but attempts to re-use this method for other purposes could cause issues.
- // TODO(robbyw): Refactor this method to also take a range, and use that.
- var range = this.fieldObject.getRange();
- if (range && range.isCollapsed() && range.getStartOffset() == 0) {
- var startNode = range.getStartNode();
- var previous = startNode.previousSibling;
- if (previous && previous.tagName == goog.dom.TagName.A) {
- bubbleTarget = previous;
- }
- }
- }
- return /** @type {Element} */ (bubbleTarget);
- };
- /**
- * Set the optional function for getting the "test" link of a url.
- * @param {function(string) : string} func The function to use.
- */
- goog.editor.plugins.LinkBubble.prototype.setTestLinkUrlFn = function(func) {
- this.testLinkUrlFn_ = func;
- };
- /**
- * Returns the target element url for the bubble.
- * @return {string} The url href.
- * @protected
- */
- goog.editor.plugins.LinkBubble.prototype.getTargetUrl = function() {
- // Get the href-attribute through getAttribute() rather than the href property
- // because Google-Toolbar on Firefox with "Send with Gmail" turned on
- // modifies the href-property of 'mailto:' links but leaves the attribute
- // untouched.
- return this.getTargetElement().getAttribute('href') || '';
- };
- /** @override */
- goog.editor.plugins.LinkBubble.prototype.getBubbleType = function() {
- return goog.dom.TagName.A;
- };
- /** @override */
- goog.editor.plugins.LinkBubble.prototype.getBubbleTitle = function() {
- return goog.ui.editor.messages.MSG_LINK_CAPTION;
- };
- /** @override */
- goog.editor.plugins.LinkBubble.prototype.createBubbleContents = function(
- bubbleContainer) {
- var linkObj = this.getLinkToTextObj_();
- // Create linkTextSpan, show plain text for e-mail address or truncate the
- // text to <= 48 characters so that property bubbles don't grow too wide and
- // create a link if URL. Only linkify valid links.
- // TODO(robbyw): Repalce this color with a CSS class.
- var color = linkObj.valid ? 'black' : 'red';
- var linkTextSpan;
- if (goog.editor.Link.isLikelyEmailAddress(linkObj.linkText) ||
- !linkObj.valid) {
- linkTextSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
- {
- id: goog.editor.plugins.LinkBubble.LINK_TEXT_ID_,
- style: 'color:' + color
- }, this.dom_.createTextNode(linkObj.linkText));
- } else {
- var testMsgSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
- {id: goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_},
- MSG_LINK_BUBBLE_TEST_LINK);
- linkTextSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
- {
- id: goog.editor.plugins.LinkBubble.LINK_TEXT_ID_,
- style: 'color:' + color
- }, '');
- var linkText = goog.string.truncateMiddle(linkObj.linkText, 48);
- // Actually creates a pseudo-link that can't be right-clicked to open in a
- // new tab, because that would avoid the logic to stop referrer leaks.
- this.createLink(goog.editor.plugins.LinkBubble.TEST_LINK_ID_,
- this.dom_.createTextNode(linkText).data,
- this.testLink,
- linkTextSpan);
- }
- var changeLinkSpan = this.createLinkOption(
- goog.editor.plugins.LinkBubble.CHANGE_LINK_SPAN_ID_);
- this.createLink(goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_,
- MSG_LINK_BUBBLE_CHANGE, this.showLinkDialog_, changeLinkSpan);
- // This function is called multiple times - we have to reset the array.
- this.actionSpans_ = [];
- for (var i = 0; i < this.extraActions_.length; i++) {
- var action = this.extraActions_[i];
- var actionSpan = this.createLinkOption(action.spanId_);
- this.actionSpans_.push(actionSpan);
- this.createLink(action.linkId_, action.message_,
- function() {
- action.actionFn_(this.getTargetUrl());
- },
- actionSpan);
- }
- var removeLinkSpan = this.createLinkOption(
- goog.editor.plugins.LinkBubble.DELETE_LINK_SPAN_ID_);
- this.createLink(goog.editor.plugins.LinkBubble.DELETE_LINK_ID_,
- MSG_LINK_BUBBLE_REMOVE, this.deleteLink_, removeLinkSpan);
- this.onShow();
- var bubbleContents = this.dom_.createDom(goog.dom.TagName.DIV,
- {id: goog.editor.plugins.LinkBubble.LINK_DIV_ID_},
- testMsgSpan || '', linkTextSpan, changeLinkSpan);
- for (i = 0; i < this.actionSpans_.length; i++) {
- bubbleContents.appendChild(this.actionSpans_[i]);
- }
- bubbleContents.appendChild(removeLinkSpan);
- goog.dom.appendChild(bubbleContainer, bubbleContents);
- };
- /**
- * Tests the link by opening it in a new tab/window. Should be used as the
- * click event handler for the test pseudo-link.
- * @protected
- */
- goog.editor.plugins.LinkBubble.prototype.testLink = function() {
- goog.window.open(this.getTestLinkAction_(),
- {
- 'target': '_blank',
- 'noreferrer': this.stopReferrerLeaks_
- }, this.fieldObject.getAppWindow());
- };
- /**
- * Returns whether the URL should be considered invalid. This always returns
- * false in the base class, and should be overridden by subclasses that wish
- * to impose validity rules on URLs.
- * @param {string} url The url to check.
- * @return {boolean} Whether the URL should be considered invalid.
- */
- goog.editor.plugins.LinkBubble.prototype.isInvalidUrl = goog.functions.FALSE;
- /**
- * Gets the text to display for a link, based on the type of link
- * @return {Object} Returns an object of the form:
- * {linkText: displayTextForLinkTarget, valid: ifTheLinkIsValid}.
- * @private
- */
- goog.editor.plugins.LinkBubble.prototype.getLinkToTextObj_ = function() {
- var isError;
- var targetUrl = this.getTargetUrl();
- if (this.isInvalidUrl(targetUrl)) {
- /**
- * @desc Message shown in a link bubble when the link is not a valid url.
- */
- var MSG_INVALID_URL_LINK_BUBBLE = goog.getMsg('invalid url');
- targetUrl = MSG_INVALID_URL_LINK_BUBBLE;
- isError = true;
- } else if (goog.editor.Link.isMailto(targetUrl)) {
- targetUrl = targetUrl.substring(7); // 7 == "mailto:".length
- }
- return {linkText: targetUrl, valid: !isError};
- };
- /**
- * Shows the link dialog
- * @private
- */
- goog.editor.plugins.LinkBubble.prototype.showLinkDialog_ = function() {
- this.fieldObject.execCommand(goog.editor.Command.MODAL_LINK_EDITOR,
- new goog.editor.Link(
- /** @type {HTMLAnchorElement} */ (this.getTargetElement()),
- false));
- this.closeBubble();
- };
- /**
- * Deletes the link associated with the bubble
- * @private
- */
- goog.editor.plugins.LinkBubble.prototype.deleteLink_ = function() {
- this.fieldObject.dispatchBeforeChange();
- var link = this.getTargetElement();
- var child = link.lastChild;
- goog.dom.flattenElement(link);
- goog.editor.range.placeCursorNextTo(child, false);
- this.closeBubble();
- this.fieldObject.dispatchChange();
- };
- /**
- * Sets the proper state for the action links.
- * @protected
- * @override
- */
- goog.editor.plugins.LinkBubble.prototype.onShow = function() {
- var linkDiv = this.dom_.getElement(
- goog.editor.plugins.LinkBubble.LINK_DIV_ID_);
- if (linkDiv) {
- var testLinkSpan = this.dom_.getElement(
- goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_);
- if (testLinkSpan) {
- var url = this.getTargetUrl();
- goog.style.showElement(testLinkSpan, !goog.editor.Link.isMailto(url));
- }
- for (var i = 0; i < this.extraActions_.length; i++) {
- var action = this.extraActions_[i];
- var actionSpan = this.dom_.getElement(action.spanId_);
- if (actionSpan) {
- goog.style.showElement(actionSpan, action.toShowFn_(
- this.getTargetUrl()));
- }
- }
- }
- };
- /**
- * Gets the url for the bubble test link. The test link is the link in the
- * bubble the user can click on to make sure the link they entered is correct.
- * @return {string} The url for the bubble link href.
- * @private
- */
- goog.editor.plugins.LinkBubble.prototype.getTestLinkAction_ = function() {
- var targetUrl = this.getTargetUrl();
- return this.testLinkUrlFn_ ? this.testLinkUrlFn_(targetUrl) : targetUrl;
- };
- /**
- * Constructor for extra actions that can be added to the link bubble.
- * @param {string} spanId The ID for the span showing the action.
- * @param {string} linkId The ID for the link showing the action.
- * @param {string} message The text for the link showing the action.
- * @param {function(string):boolean} toShowFn Test function to determine whether
- * to show the action for the given URL.
- * @param {function(string):void} actionFn Action function to run when the
- * action is clicked. Takes the current target URL as a parameter.
- * @constructor
- */
- goog.editor.plugins.LinkBubble.Action = function(spanId, linkId, message,
- toShowFn, actionFn) {
- this.spanId_ = spanId;
- this.linkId_ = linkId;
- this.message_ = message;
- this.toShowFn_ = toShowFn;
- this.actionFn_ = actionFn;
- };