// 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 A dialog for editing/creating a link. * * @author robbyw@google.com (Robby Walker) */ goog.provide('goog.ui.editor.LinkDialog'); goog.provide('goog.ui.editor.LinkDialog.BeforeTestLinkEvent'); goog.provide('goog.ui.editor.LinkDialog.EventType'); goog.provide('goog.ui.editor.LinkDialog.OkEvent'); goog.require('goog.a11y.aria'); goog.require('goog.a11y.aria.State'); goog.require('goog.dom'); goog.require('goog.dom.InputType'); goog.require('goog.dom.TagName'); goog.require('goog.dom.safe'); goog.require('goog.editor.BrowserFeature'); goog.require('goog.editor.Link'); goog.require('goog.editor.focus'); goog.require('goog.editor.node'); goog.require('goog.events.Event'); goog.require('goog.events.EventHandler'); goog.require('goog.events.InputHandler'); goog.require('goog.html.SafeHtml'); goog.require('goog.html.SafeHtmlFormatter'); goog.require('goog.string'); goog.require('goog.string.Unicode'); goog.require('goog.style'); goog.require('goog.ui.Button'); goog.require('goog.ui.Component'); goog.require('goog.ui.LinkButtonRenderer'); goog.require('goog.ui.editor.AbstractDialog'); goog.require('goog.ui.editor.TabPane'); goog.require('goog.ui.editor.messages'); goog.require('goog.userAgent'); goog.require('goog.window'); /** * A type of goog.ui.editor.AbstractDialog for editing/creating a link. * @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the * dialog's dom structure. * @param {goog.editor.Link} link The target link. * @constructor * @extends {goog.ui.editor.AbstractDialog} * @final */ goog.ui.editor.LinkDialog = function(domHelper, link) { goog.ui.editor.LinkDialog.base(this, 'constructor', domHelper); /** * The link being modified by this dialog. * @type {goog.editor.Link} * @private */ this.targetLink_ = link; /** * The event handler for this dialog. * @type {goog.events.EventHandler} * @private */ this.eventHandler_ = new goog.events.EventHandler(this); this.registerDisposable(this.eventHandler_); /** * Optional warning to show about email addresses. * @type {goog.html.SafeHtml} * @private */ this.emailWarning_ = null; /** * Whether to show a checkbox where the user can choose to have the link open * in a new window. * @type {boolean} * @private */ this.showOpenLinkInNewWindow_ = false; /** * Whether the "open link in new window" checkbox should be checked when the * dialog is shown, and also whether it was checked last time the dialog was * closed. * @type {boolean} * @private */ this.isOpenLinkInNewWindowChecked_ = false; /** * Whether to show a checkbox where the user can choose to have 'rel=nofollow' * attribute added to the link. * @type {boolean} * @private */ this.showRelNoFollow_ = false; /** * InputHandler object to listen for changes in the url input field. * @type {goog.events.InputHandler} * @private */ this.urlInputHandler_ = null; /** * InputHandler object to listen for changes in the email input field. * @type {goog.events.InputHandler} * @private */ this.emailInputHandler_ = null; /** * InputHandler object to listen for changes in the text to display input * field. * @type {goog.events.InputHandler} * @private */ this.textInputHandler_ = null; /** * The tab bar where the url and email tabs are. * @type {goog.ui.editor.TabPane} * @private */ this.tabPane_ = null; /** * The div element holding the link's display text input. * @type {HTMLDivElement} * @private */ this.textToDisplayDiv_ = null; /** * The input element holding the link's display text. * @type {HTMLInputElement} * @private */ this.textToDisplayInput_ = null; /** * Whether or not the feature of automatically generating the display text is * enabled. * @type {boolean} * @private */ this.autogenFeatureEnabled_ = true; /** * Whether or not we should automatically generate the display text. * @type {boolean} * @private */ this.autogenerateTextToDisplay_ = false; /** * Whether or not automatic generation of the display text is disabled. * @type {boolean} * @private */ this.disableAutogen_ = false; /** * The input element (checkbox) to indicate that the link should open in a new * window. * @type {HTMLInputElement} * @private */ this.openInNewWindowCheckbox_ = null; /** * The input element (checkbox) to indicate that the link should have * 'rel=nofollow' attribute. * @type {HTMLInputElement} * @private */ this.relNoFollowCheckbox_ = null; /** * Whether to stop leaking the page's url via the referrer header when the * "test this link" link is clicked. * @type {boolean} * @private */ this.stopReferrerLeaks_ = false; }; goog.inherits(goog.ui.editor.LinkDialog, goog.ui.editor.AbstractDialog); /** * Events specific to the link dialog. * @enum {string} */ goog.ui.editor.LinkDialog.EventType = { BEFORE_TEST_LINK: 'beforetestlink' }; /** * OK event object for the link dialog. * @param {string} linkText Text the user chose to display for the link. * @param {string} linkUrl Url the user chose for the link to point to. * @param {boolean} openInNewWindow Whether the link should open in a new window * when clicked. * @param {boolean} noFollow Whether the link should have 'rel=nofollow' * attribute. * @constructor * @extends {goog.events.Event} * @final */ goog.ui.editor.LinkDialog.OkEvent = function( linkText, linkUrl, openInNewWindow, noFollow) { goog.ui.editor.LinkDialog.OkEvent.base( this, 'constructor', goog.ui.editor.AbstractDialog.EventType.OK); /** * The text of the link edited in the dialog. * @type {string} */ this.linkText = linkText; /** * The url of the link edited in the dialog. * @type {string} */ this.linkUrl = linkUrl; /** * Whether the link should open in a new window when clicked. * @type {boolean} */ this.openInNewWindow = openInNewWindow; /** * Whether the link should have 'rel=nofollow' attribute. * @type {boolean} */ this.noFollow = noFollow; }; goog.inherits(goog.ui.editor.LinkDialog.OkEvent, goog.events.Event); /** * Event fired before testing a link by opening it in another window. * Calling preventDefault will stop the link from being opened. * @param {string} url Url of the link being tested. * @constructor * @extends {goog.events.Event} * @final */ goog.ui.editor.LinkDialog.BeforeTestLinkEvent = function(url) { goog.ui.editor.LinkDialog.BeforeTestLinkEvent.base( this, 'constructor', goog.ui.editor.LinkDialog.EventType.BEFORE_TEST_LINK); /** * The url of the link being tested. * @type {string} */ this.url = url; }; goog.inherits(goog.ui.editor.LinkDialog.BeforeTestLinkEvent, goog.events.Event); /** * Sets the warning message to show to users about including email addresses on * public web pages. * @param {!goog.html.SafeHtml} emailWarning Warning message to show users about * including email addresses on the web. */ goog.ui.editor.LinkDialog.prototype.setEmailWarning = function(emailWarning) { this.emailWarning_ = emailWarning; }; /** * Tells the dialog to show a checkbox where the user can choose to have the * link open in a new window. * @param {boolean} startChecked Whether to check the checkbox the first * time the dialog is shown. Subesquent times the checkbox will remember its * previous state. */ goog.ui.editor.LinkDialog.prototype.showOpenLinkInNewWindow = function( startChecked) { this.showOpenLinkInNewWindow_ = true; this.isOpenLinkInNewWindowChecked_ = startChecked; }; /** * Tells the dialog to show a checkbox where the user can choose to add * 'rel=nofollow' attribute to the link. */ goog.ui.editor.LinkDialog.prototype.showRelNoFollow = function() { this.showRelNoFollow_ = true; }; /** @override */ goog.ui.editor.LinkDialog.prototype.show = function() { goog.ui.editor.LinkDialog.base(this, 'show'); this.selectAppropriateTab_( this.textToDisplayInput_.value, this.getTargetUrl_()); this.syncOkButton_(); if (this.showOpenLinkInNewWindow_) { if (!this.targetLink_.isNew()) { // If link is not new, checkbox should reflect current target. this.isOpenLinkInNewWindowChecked_ = this.targetLink_.getAnchor().target == '_blank'; } this.openInNewWindowCheckbox_.checked = this.isOpenLinkInNewWindowChecked_; } if (this.showRelNoFollow_) { this.relNoFollowCheckbox_.checked = goog.ui.editor.LinkDialog.hasNoFollow(this.targetLink_.getAnchor().rel); } }; /** @override */ goog.ui.editor.LinkDialog.prototype.hide = function() { this.disableAutogenFlag_(false); goog.ui.editor.LinkDialog.base(this, 'hide'); }; /** * Tells the dialog whether to show the 'text to display' div. * When the target element of the dialog is an image, there is no link text * to modify. This function can be used for this kind of situations. * @param {boolean} visible Whether to make 'text to display' div visible. */ goog.ui.editor.LinkDialog.prototype.setTextToDisplayVisible = function( visible) { if (this.textToDisplayDiv_) { goog.style.setStyle( this.textToDisplayDiv_, 'display', visible ? 'block' : 'none'); } }; /** * Tells the plugin whether to stop leaking the page's url via the referrer * header when the "test this link" link is clicked. * @param {boolean} stop Whether to stop leaking the referrer. */ goog.ui.editor.LinkDialog.prototype.setStopReferrerLeaks = function(stop) { this.stopReferrerLeaks_ = stop; }; /** * Tells the dialog whether the autogeneration of text to display is to be * enabled. * @param {boolean} enable Whether to enable the feature. */ goog.ui.editor.LinkDialog.prototype.setAutogenFeatureEnabled = function( enable) { this.autogenFeatureEnabled_ = enable; }; /** * Checks if {@code str} contains {@code "nofollow"} as a separate word. * @param {string} str String to be tested. This is usually {@code rel} * attribute of an {@code HTMLAnchorElement} object. * @return {boolean} {@code true} if {@code str} contains {@code nofollow}. */ goog.ui.editor.LinkDialog.hasNoFollow = function(str) { return goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_.test(str); }; /** * Removes {@code "nofollow"} from {@code rel} if it's present as a separate * word. * @param {string} rel Input string. This is usually {@code rel} attribute of * an {@code HTMLAnchorElement} object. * @return {string} {@code rel} with any {@code "nofollow"} removed. */ goog.ui.editor.LinkDialog.removeNoFollow = function(rel) { return rel.replace(goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_, ''); }; // *** Protected interface ************************************************** // /** @override */ goog.ui.editor.LinkDialog.prototype.createDialogControl = function() { var builder = new goog.ui.editor.AbstractDialog.Builder(this); builder.setTitle(goog.ui.editor.messages.MSG_EDIT_LINK) .setContent(this.createDialogContent_()); return builder.build(); }; /** * Creates and returns the event object to be used when dispatching the OK * event to listeners based on which tab is currently selected and the contents * of the input fields of that tab. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when * dispatching the OK event to listeners. * @protected * @override */ goog.ui.editor.LinkDialog.prototype.createOkEvent = function() { if (this.tabPane_.getCurrentTabId() == goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB) { return this.createOkEventFromEmailTab_(); } else { return this.createOkEventFromWebTab_(); } }; // *** Private implementation *********************************************** // /** * Regular expression that matches {@code nofollow} value in an * {@code * HTMLAnchorElement}'s {@code rel} element. * @type {RegExp} * @private */ goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_ = /\bnofollow\b/i; /** * Creates contents of this dialog. * @return {!Element} Contents of the dialog as a DOM element. * @private */ goog.ui.editor.LinkDialog.prototype.createDialogContent_ = function() { this.textToDisplayDiv_ = /** @type {!HTMLDivElement} */ (this.buildTextToDisplayDiv_()); var content = this.dom.createDom(goog.dom.TagName.DIV, null, this.textToDisplayDiv_); this.tabPane_ = new goog.ui.editor.TabPane(this.dom, goog.ui.editor.messages.MSG_LINK_TO); this.registerDisposable(this.tabPane_); this.tabPane_.addTab( goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB, goog.ui.editor.messages.MSG_ON_THE_WEB, goog.ui.editor.messages.MSG_ON_THE_WEB_TIP, goog.ui.editor.LinkDialog.BUTTON_GROUP_, this.buildTabOnTheWeb_()); this.tabPane_.addTab( goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB, goog.ui.editor.messages.MSG_EMAIL_ADDRESS, goog.ui.editor.messages.MSG_EMAIL_ADDRESS_TIP, goog.ui.editor.LinkDialog.BUTTON_GROUP_, this.buildTabEmailAddress_()); this.tabPane_.render(content); this.eventHandler_.listen( this.tabPane_, goog.ui.Component.EventType.SELECT, this.onChangeTab_); if (this.showOpenLinkInNewWindow_) { content.appendChild(this.buildOpenInNewWindowDiv_()); } if (this.showRelNoFollow_) { content.appendChild(this.buildRelNoFollowDiv_()); } return content; }; /** * Builds and returns the text to display section of the edit link dialog. * @return {!Element} A div element to be appended into the dialog div. * @private */ goog.ui.editor.LinkDialog.prototype.buildTextToDisplayDiv_ = function() { var table = this.dom.createTable(1, 2); table.cellSpacing = '0'; table.cellPadding = '0'; table.style.fontSize = '10pt'; // Build the text to display input. var textToDisplayDiv = this.dom.createDom(goog.dom.TagName.DIV); var html = goog.html.SafeHtml.create( 'span', { 'style': { 'position': 'relative', 'bottom': '2px', 'padding-right': '1px', 'white-space': 'nowrap' }, id: goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY_LABEL }, [goog.ui.editor.messages.MSG_TEXT_TO_DISPLAY, goog.string.Unicode.NBSP]); goog.dom.safe.setInnerHtml(table.rows[0].cells[0], html); this.textToDisplayInput_ = this.dom.createDom( goog.dom.TagName.INPUT, {id: goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY}); var textInput = this.textToDisplayInput_; // 98% prevents scroll bars in standards mode. // TODO(robbyw): Is this necessary for quirks mode? goog.style.setStyle(textInput, 'width', '98%'); goog.style.setStyle(table.rows[0].cells[1], 'width', '100%'); goog.dom.appendChild(table.rows[0].cells[1], textInput); goog.a11y.aria.setState( /** @type {!Element} */ (textInput), goog.a11y.aria.State.LABELLEDBY, goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY_LABEL); textInput.value = this.targetLink_.getCurrentText(); this.textInputHandler_ = new goog.events.InputHandler(textInput); this.registerDisposable(this.textInputHandler_); this.eventHandler_.listen( this.textInputHandler_, goog.events.InputHandler.EventType.INPUT, this.onTextToDisplayEdit_); goog.dom.appendChild(textToDisplayDiv, table); return textToDisplayDiv; }; /** * Builds and returns the "checkbox to open the link in a new window" section of * the edit link dialog. * @return {!Element} A div element to be appended into the dialog div. * @private */ goog.ui.editor.LinkDialog.prototype.buildOpenInNewWindowDiv_ = function() { this.openInNewWindowCheckbox_ = this.dom.createDom( goog.dom.TagName.INPUT, {'type': goog.dom.InputType.CHECKBOX}); return this.dom.createDom( goog.dom.TagName.DIV, null, this.dom.createDom( goog.dom.TagName.LABEL, null, this.openInNewWindowCheckbox_, goog.ui.editor.messages.MSG_OPEN_IN_NEW_WINDOW)); }; /** * Creates a DIV with a checkbox for {@code rel=nofollow} option. * @return {!Element} Newly created DIV element. * @private */ goog.ui.editor.LinkDialog.prototype.buildRelNoFollowDiv_ = function() { var formatter = new goog.html.SafeHtmlFormatter(); /** @desc Checkbox text for adding 'rel=nofollow' attribute to a link. */ var MSG_ADD_REL_NOFOLLOW_ATTR = goog.getMsg( "Add '{$relNoFollow}' attribute ({$linkStart}Learn more{$linkEnd})", { 'relNoFollow': 'rel=nofollow', 'linkStart': formatter.startTag('a', { 'href': 'http://support.google.com/webmasters/bin/' + 'answer.py?hl=en&answer=96569', 'target': '_blank' }), 'linkEnd': formatter.endTag('a') }); this.relNoFollowCheckbox_ = this.dom.createDom( goog.dom.TagName.INPUT, {'type': goog.dom.InputType.CHECKBOX}); return this.dom.createDom( goog.dom.TagName.DIV, null, this.dom.createDom( goog.dom.TagName.LABEL, null, this.relNoFollowCheckbox_, goog.dom.safeHtmlToNode( formatter.format(MSG_ADD_REL_NOFOLLOW_ATTR)))); }; /** * Builds and returns the div containing the tab "On the web". * @return {!Element} The div element containing the tab. * @private */ goog.ui.editor.LinkDialog.prototype.buildTabOnTheWeb_ = function() { var onTheWebDiv = this.dom.createElement(goog.dom.TagName.DIV); var headingDiv = this.dom.createDom( goog.dom.TagName.DIV, {}, this.dom.createDom( goog.dom.TagName.B, {}, goog.ui.editor.messages.MSG_WHAT_URL)); var urlInput = this.dom.createDom(goog.dom.TagName.INPUT, { id: goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT, className: goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_ }); goog.a11y.aria.setState( urlInput, goog.a11y.aria.State.LABELLEDBY, goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB); // IE throws on unknown values for type, but IE10+ supports type=url if (!goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10')) { // On browsers that support Web Forms 2.0, allow autocompletion of URLs. urlInput.type = goog.dom.InputType.URL; } if (goog.editor.BrowserFeature.NEEDS_99_WIDTH_IN_STANDARDS_MODE && goog.editor.node.isStandardsMode(urlInput)) { urlInput.style.width = '99%'; } var inputDiv = this.dom.createDom(goog.dom.TagName.DIV, null, urlInput); this.urlInputHandler_ = new goog.events.InputHandler(urlInput); this.registerDisposable(this.urlInputHandler_); this.eventHandler_.listen( this.urlInputHandler_, goog.events.InputHandler.EventType.INPUT, this.onUrlOrEmailInputChange_); var testLink = new goog.ui.Button( goog.ui.editor.messages.MSG_TEST_THIS_LINK, goog.ui.LinkButtonRenderer.getInstance(), this.dom); testLink.render(inputDiv); testLink.getElement().style.marginTop = '1em'; this.eventHandler_.listen( testLink, goog.ui.Component.EventType.ACTION, this.onWebTestLink_); // Build the "On the web" explanation text div. var explanationDiv = this.dom.createDom( goog.dom.TagName.DIV, goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_); goog.dom.safe.setInnerHtml( explanationDiv, goog.ui.editor.messages.getTrLinkExplanationSafeHtml()); onTheWebDiv.appendChild(headingDiv); onTheWebDiv.appendChild(inputDiv); onTheWebDiv.appendChild(explanationDiv); return onTheWebDiv; }; /** * Builds and returns the div containing the tab "Email address". * @return {!Element} the div element containing the tab. * @private */ goog.ui.editor.LinkDialog.prototype.buildTabEmailAddress_ = function() { var emailTab = this.dom.createDom(goog.dom.TagName.DIV); var headingDiv = this.dom.createDom( goog.dom.TagName.DIV, {}, this.dom.createDom( goog.dom.TagName.B, {}, goog.ui.editor.messages.MSG_WHAT_EMAIL)); goog.dom.appendChild(emailTab, headingDiv); var emailInput = this.dom.createDom(goog.dom.TagName.INPUT, { id: goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT, className: goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_ }); goog.a11y.aria.setState( emailInput, goog.a11y.aria.State.LABELLEDBY, goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB); if (goog.editor.BrowserFeature.NEEDS_99_WIDTH_IN_STANDARDS_MODE && goog.editor.node.isStandardsMode(emailInput)) { // Standards mode sizes this too large. emailInput.style.width = '99%'; } goog.dom.appendChild(emailTab, emailInput); this.emailInputHandler_ = new goog.events.InputHandler(emailInput); this.registerDisposable(this.emailInputHandler_); this.eventHandler_.listen( this.emailInputHandler_, goog.events.InputHandler.EventType.INPUT, this.onUrlOrEmailInputChange_); goog.dom.appendChild( emailTab, this.dom.createDom( goog.dom.TagName.DIV, { id: goog.ui.editor.LinkDialog.Id_.EMAIL_WARNING, className: goog.ui.editor.LinkDialog.EMAIL_WARNING_CLASSNAME_, style: 'visibility:hidden' }, goog.ui.editor.messages.MSG_INVALID_EMAIL)); if (this.emailWarning_) { var explanationDiv = this.dom.createDom( goog.dom.TagName.DIV, goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_); goog.dom.safe.setInnerHtml(explanationDiv, this.emailWarning_); goog.dom.appendChild(emailTab, explanationDiv); } return emailTab; }; /** * Returns the url that the target points to. * @return {string} The url that the target points to. * @private */ goog.ui.editor.LinkDialog.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.targetLink_.getAnchor().getAttribute('href') || ''; }; /** * Selects the correct tab based on the URL, and fills in its inputs. * For new links, it suggests a url based on the link text. * @param {string} text The inner text of the link. * @param {string} url The href for the link. * @private */ goog.ui.editor.LinkDialog.prototype.selectAppropriateTab_ = function( text, url) { if (this.isNewLink_()) { // Newly created non-empty link: try to infer URL from the link text. this.guessUrlAndSelectTab_(text); } else if (goog.editor.Link.isMailto(url)) { // The link is for an email. this.tabPane_.setSelectedTabId( goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB); this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT) .value = url.substring(url.indexOf(':') + 1); this.setAutogenFlagFromCurInput_(); } else { // No specific tab was appropriate, default to on the web tab. this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB); this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value = this.isNewLink_() ? 'http://' : url; this.setAutogenFlagFromCurInput_(); } }; /** * Select a url/tab based on the link's text. This function is simply * the isNewLink_() == true case of selectAppropriateTab_(). * @param {string} text The inner text of the link. * @private */ goog.ui.editor.LinkDialog.prototype.guessUrlAndSelectTab_ = function(text) { if (goog.editor.Link.isLikelyEmailAddress(text)) { // The text is for an email address. this.tabPane_.setSelectedTabId( goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB); this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT) .value = text; this.setAutogenFlag_(true); // TODO(user): Why disable right after enabling? What bug are we // working around? this.disableAutogenFlag_(true); } else if (goog.editor.Link.isLikelyUrl(text)) { // The text is for a web URL. this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB); this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value = text; this.setAutogenFlag_(true); this.disableAutogenFlag_(true); } else { // No meaning could be deduced from text, choose a default tab. if (!this.targetLink_.getCurrentText()) { this.setAutogenFlag_(true); } this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB); } }; /** * Called on a change to the url or email input. If either one of those tabs * is active, sets the OK button to enabled/disabled accordingly. * @private */ goog.ui.editor.LinkDialog.prototype.syncOkButton_ = function() { var inputValue; if (this.tabPane_.getCurrentTabId() == goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB) { inputValue = this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT) .value; this.toggleInvalidEmailWarning_( inputValue != '' && !goog.editor.Link.isLikelyEmailAddress(inputValue)); } else if ( this.tabPane_.getCurrentTabId() == goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB) { inputValue = this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value; } else { return; } this.getOkButtonElement().disabled = goog.string.isEmptyOrWhitespace(inputValue); }; /** * Show/hide the Invalid Email Address warning. * @param {boolean} on Whether to show the warning. * @private */ goog.ui.editor.LinkDialog.prototype.toggleInvalidEmailWarning_ = function(on) { this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_WARNING) .style.visibility = (on ? 'visible' : 'hidden'); }; /** * Changes the autogenerateTextToDisplay flag so that text to * display stops autogenerating. * @private */ goog.ui.editor.LinkDialog.prototype.onTextToDisplayEdit_ = function() { var inputEmpty = this.textToDisplayInput_.value == ''; if (inputEmpty) { this.setAutogenFlag_(true); } else { this.setAutogenFlagFromCurInput_(); } }; /** * The function called when hitting OK with the "On the web" tab current. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when * dispatching the OK event to listeners. * @private */ goog.ui.editor.LinkDialog.prototype.createOkEventFromWebTab_ = function() { var input = /** @type {HTMLInputElement} */ ( this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT)); var linkURL = input.value; if (goog.editor.Link.isLikelyEmailAddress(linkURL)) { // Make sure that if user types in an e-mail address, it becomes "mailto:". return this.createOkEventFromEmailTab_( goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT); } else { if (linkURL.search(/:/) < 0) { linkURL = 'http://' + goog.string.trimLeft(linkURL); } return this.createOkEventFromUrl_(linkURL); } }; /** * The function called when hitting OK with the "email address" tab current. * @param {string=} opt_inputId Id of an alternate input to check. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when * dispatching the OK event to listeners. * @private */ goog.ui.editor.LinkDialog.prototype.createOkEventFromEmailTab_ = function( opt_inputId) { var linkURL = this.dom .getElement( opt_inputId || goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT) .value; linkURL = 'mailto:' + linkURL; return this.createOkEventFromUrl_(linkURL); }; /** * Function to test a link from the on the web tab. * @private */ goog.ui.editor.LinkDialog.prototype.onWebTestLink_ = function() { var input = /** @type {HTMLInputElement} */ ( this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT)); var url = input.value; if (url.search(/:/) < 0) { url = 'http://' + goog.string.trimLeft(url); } if (this.dispatchEvent( new goog.ui.editor.LinkDialog.BeforeTestLinkEvent(url))) { var win = this.dom.getWindow(); var size = goog.dom.getViewportSize(win); var openOptions = { target: '_blank', width: Math.max(size.width - 50, 50), height: Math.max(size.height - 50, 50), toolbar: true, scrollbars: true, location: true, statusbar: false, menubar: true, 'resizable': true, 'noreferrer': this.stopReferrerLeaks_ }; goog.window.open(url, openOptions, win); } }; /** * Called whenever the url or email input is edited. If the text to display * matches the text to display, turn on auto. Otherwise if auto is on, update * the text to display based on the url. * @private */ goog.ui.editor.LinkDialog.prototype.onUrlOrEmailInputChange_ = function() { if (this.autogenerateTextToDisplay_) { this.setTextToDisplayFromAuto_(); } else if (this.textToDisplayInput_.value == '') { this.setAutogenFlagFromCurInput_(); } this.syncOkButton_(); }; /** * Called when the currently selected tab changes. * @param {goog.events.Event} e The tab change event. * @private */ goog.ui.editor.LinkDialog.prototype.onChangeTab_ = function(e) { var tab = /** @type {goog.ui.Tab} */ (e.target); // Focus on the input field in the selected tab. var input = /** @type {!HTMLElement} */ ( this.dom.getElement( tab.getId() + goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX)); goog.editor.focus.focusInputField(input); // For some reason, IE does not fire onpropertychange events when the width // is specified as a percentage, which breaks the InputHandlers. input.style.width = ''; input.style.width = input.offsetWidth + 'px'; this.syncOkButton_(); this.setTextToDisplayFromAuto_(); }; /** * If autogen is turned on, set the value of text to display based on the * current selection or url. * @private */ goog.ui.editor.LinkDialog.prototype.setTextToDisplayFromAuto_ = function() { if (this.autogenFeatureEnabled_ && this.autogenerateTextToDisplay_) { var inputId = this.tabPane_.getCurrentTabId() + goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX; this.textToDisplayInput_.value = /** @type {HTMLInputElement} */ (this.dom.getElement(inputId)).value; } }; /** * Turn on the autogenerate text to display flag, and set some sort of indicator * that autogen is on. * @param {boolean} val Boolean value to set autogenerate to. * @private */ goog.ui.editor.LinkDialog.prototype.setAutogenFlag_ = function(val) { // TODO(user): This whole autogen thing is very confusing. It needs // to be refactored and/or explained. this.autogenerateTextToDisplay_ = val; }; /** * Disables autogen so that onUrlOrEmailInputChange_ doesn't act in cases * that are undesirable. * @param {boolean} autogen Boolean value to set disableAutogen to. * @private */ goog.ui.editor.LinkDialog.prototype.disableAutogenFlag_ = function(autogen) { this.setAutogenFlag_(!autogen); this.disableAutogen_ = autogen; }; /** * Creates an OK event from the text to display input and the specified link. * If text to display input is empty, then generate the auto value for it. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when * dispatching the OK event to listeners. * @param {string} url Url the target element should point to. * @private */ goog.ui.editor.LinkDialog.prototype.createOkEventFromUrl_ = function(url) { // Fill in the text to display input in case it is empty. this.setTextToDisplayFromAuto_(); if (this.showOpenLinkInNewWindow_) { // Save checkbox state for next time. this.isOpenLinkInNewWindowChecked_ = this.openInNewWindowCheckbox_.checked; } return new goog.ui.editor.LinkDialog.OkEvent( this.textToDisplayInput_.value, url, this.showOpenLinkInNewWindow_ && this.isOpenLinkInNewWindowChecked_, this.showRelNoFollow_ && this.relNoFollowCheckbox_.checked); }; /** * If an email or url is being edited, set autogenerate to on if the text to * display matches the url. * @private */ goog.ui.editor.LinkDialog.prototype.setAutogenFlagFromCurInput_ = function() { var autogen = false; if (!this.disableAutogen_) { var tabInput = this.dom.getElement( this.tabPane_.getCurrentTabId() + goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX); autogen = (tabInput.value == this.textToDisplayInput_.value); } this.setAutogenFlag_(autogen); }; /** * @return {boolean} Whether the link is new. * @private */ goog.ui.editor.LinkDialog.prototype.isNewLink_ = function() { return this.targetLink_.isNew(); }; /** * IDs for relevant DOM elements. * @enum {string} * @private */ goog.ui.editor.LinkDialog.Id_ = { TEXT_TO_DISPLAY: 'linkdialog-text', TEXT_TO_DISPLAY_LABEL: 'linkdialog-text-label', ON_WEB_TAB: 'linkdialog-onweb', ON_WEB_INPUT: 'linkdialog-onweb-tab-input', EMAIL_ADDRESS_TAB: 'linkdialog-email', EMAIL_ADDRESS_INPUT: 'linkdialog-email-tab-input', EMAIL_WARNING: 'linkdialog-email-warning', TAB_INPUT_SUFFIX: '-tab-input' }; /** * Base name for the radio buttons group. * @type {string} * @private */ goog.ui.editor.LinkDialog.BUTTON_GROUP_ = 'linkdialog-buttons'; /** * Class name for the url and email input elements. * @type {string} * @private */ goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_ = goog.getCssName('tr-link-dialog-target-input'); /** * Class name for the email address warning element. * @type {string} * @private */ goog.ui.editor.LinkDialog.EMAIL_WARNING_CLASSNAME_ = goog.getCssName('tr-link-dialog-email-warning'); /** * Class name for the explanation text elements. * @type {string} * @private */ goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_ = goog.getCssName('tr-link-dialog-explanation-text');