Path: blob/trunk/third_party/closure/goog/editor/plugins/linkdialogplugin.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A plugin for the LinkDialog.16*17* @author [email protected] (Nick Santos)18* @author [email protected] (Robby Walker)19*/2021goog.provide('goog.editor.plugins.LinkDialogPlugin');2223goog.require('goog.array');24goog.require('goog.dom');25goog.require('goog.editor.Command');26goog.require('goog.editor.plugins.AbstractDialogPlugin');27goog.require('goog.events.EventHandler');28goog.require('goog.functions');29goog.require('goog.ui.editor.AbstractDialog');30goog.require('goog.ui.editor.LinkDialog');31goog.require('goog.uri.utils');32333435/**36* A plugin that opens the link dialog.37* @constructor38* @extends {goog.editor.plugins.AbstractDialogPlugin}39*/40goog.editor.plugins.LinkDialogPlugin = function() {41goog.editor.plugins.LinkDialogPlugin.base(42this, 'constructor', goog.editor.Command.MODAL_LINK_EDITOR);4344/**45* Event handler for this object.46* @type {goog.events.EventHandler<!goog.editor.plugins.LinkDialogPlugin>}47* @private48*/49this.eventHandler_ = new goog.events.EventHandler(this);505152/**53* A list of whitelisted URL schemes which are safe to open.54* @type {Array<string>}55* @private56*/57this.safeToOpenSchemes_ = ['http', 'https', 'ftp'];58};59goog.inherits(60goog.editor.plugins.LinkDialogPlugin,61goog.editor.plugins.AbstractDialogPlugin);626364/**65* Link object that the dialog is editing.66* @type {goog.editor.Link}67* @protected68*/69goog.editor.plugins.LinkDialogPlugin.prototype.currentLink_;707172/**73* Optional warning to show about email addresses.74* @type {goog.html.SafeHtml}75* @private76*/77goog.editor.plugins.LinkDialogPlugin.prototype.emailWarning_;787980/**81* Whether to show a checkbox where the user can choose to have the link open in82* a new window.83* @type {boolean}84* @private85*/86goog.editor.plugins.LinkDialogPlugin.prototype.showOpenLinkInNewWindow_ = false;878889/**90* Whether the "open link in new window" checkbox should be checked when the91* dialog is shown, and also whether it was checked last time the dialog was92* closed.93* @type {boolean}94* @private95*/96goog.editor.plugins.LinkDialogPlugin.prototype.isOpenLinkInNewWindowChecked_ =97false;9899100/**101* Weather to show a checkbox where the user can choose to add 'rel=nofollow'102* attribute added to the link.103* @type {boolean}104* @private105*/106goog.editor.plugins.LinkDialogPlugin.prototype.showRelNoFollow_ = false;107108109/**110* Whether to stop referrer leaks. Defaults to false.111* @type {boolean}112* @private113*/114goog.editor.plugins.LinkDialogPlugin.prototype.stopReferrerLeaks_ = false;115116117/**118* Whether to block opening links with a non-whitelisted URL scheme.119* @type {boolean}120* @private121*/122goog.editor.plugins.LinkDialogPlugin.prototype.blockOpeningUnsafeSchemes_ =123true;124125126/** @override */127goog.editor.plugins.LinkDialogPlugin.prototype.getTrogClassId =128goog.functions.constant('LinkDialogPlugin');129130131/**132* Tells the plugin whether to block URLs with schemes not in the whitelist.133* If blocking is enabled, this plugin will stop the 'Test Link' popup134* window from being created. Blocking doesn't affect link creation--if the135* user clicks the 'OK' button with an unsafe URL, the link will still be136* created as normal.137* @param {boolean} blockOpeningUnsafeSchemes Whether to block non-whitelisted138* schemes.139*/140goog.editor.plugins.LinkDialogPlugin.prototype.setBlockOpeningUnsafeSchemes =141function(blockOpeningUnsafeSchemes) {142this.blockOpeningUnsafeSchemes_ = blockOpeningUnsafeSchemes;143};144145146/**147* Sets a whitelist of allowed URL schemes that are safe to open.148* Schemes should all be in lowercase. If the plugin is set to block opening149* unsafe schemes, user-entered URLs will be converted to lowercase and checked150* against this list. The whitelist has no effect if blocking is not enabled.151* @param {Array<string>} schemes String array of URL schemes to allow (http,152* https, etc.).153*/154goog.editor.plugins.LinkDialogPlugin.prototype.setSafeToOpenSchemes = function(155schemes) {156this.safeToOpenSchemes_ = schemes;157};158159160/**161* Tells the dialog to show a checkbox where the user can choose to have the162* link open in a new window.163* @param {boolean} startChecked Whether to check the checkbox the first164* time the dialog is shown. Subesquent times the checkbox will remember its165* previous state.166*/167goog.editor.plugins.LinkDialogPlugin.prototype.showOpenLinkInNewWindow =168function(startChecked) {169this.showOpenLinkInNewWindow_ = true;170this.isOpenLinkInNewWindowChecked_ = startChecked;171};172173174/**175* Tells the dialog to show a checkbox where the user can choose to have176* 'rel=nofollow' attribute added to the link.177*/178goog.editor.plugins.LinkDialogPlugin.prototype.showRelNoFollow = function() {179this.showRelNoFollow_ = true;180};181182183/**184* Returns whether the"open link in new window" checkbox was checked last time185* the dialog was closed.186* @return {boolean} Whether the"open link in new window" checkbox was checked187* last time the dialog was closed.188*/189goog.editor.plugins.LinkDialogPlugin.prototype190.getOpenLinkInNewWindowCheckedState = function() {191return this.isOpenLinkInNewWindowChecked_;192};193194195/**196* Tells the plugin to stop leaking the page's url via the referrer header when197* the "test this link" link is clicked. When the user clicks on a link, the198* browser makes a request for the link url, passing the url of the current page199* in the request headers. If the user wants the current url to be kept secret200* (e.g. an unpublished document), the owner of the url that was clicked will201* see the secret url in the request headers, and it will no longer be a secret.202* Calling this method will not send a referrer header in the request, just as203* if the user had opened a blank window and typed the url in themselves.204*/205goog.editor.plugins.LinkDialogPlugin.prototype.stopReferrerLeaks = function() {206this.stopReferrerLeaks_ = true;207};208209210/**211* Sets the warning message to show to users about including email addresses on212* public web pages.213* @param {!goog.html.SafeHtml} emailWarning Warning message to show users about214* including email addresses on the web.215*/216goog.editor.plugins.LinkDialogPlugin.prototype.setEmailWarning = function(217emailWarning) {218this.emailWarning_ = emailWarning;219};220221222/**223* Handles execCommand by opening the dialog.224* @param {string} command The command to execute.225* @param {*=} opt_arg {@link A goog.editor.Link} object representing the link226* being edited.227* @return {*} Always returns true, indicating the dialog was shown.228* @protected229* @override230*/231goog.editor.plugins.LinkDialogPlugin.prototype.execCommandInternal = function(232command, opt_arg) {233this.currentLink_ = /** @type {goog.editor.Link} */ (opt_arg);234return goog.editor.plugins.LinkDialogPlugin.base(235this, 'execCommandInternal', command, opt_arg);236};237238239/**240* Handles when the dialog closes.241* @param {goog.events.Event} e The AFTER_HIDE event object.242* @override243* @protected244*/245goog.editor.plugins.LinkDialogPlugin.prototype.handleAfterHide = function(e) {246goog.editor.plugins.LinkDialogPlugin.base(this, 'handleAfterHide', e);247this.currentLink_ = null;248};249250251/**252* @return {goog.events.EventHandler<T>} The event handler.253* @protected254* @this {T}255* @template T256*/257goog.editor.plugins.LinkDialogPlugin.prototype.getEventHandler = function() {258return this.eventHandler_;259};260261262/**263* @return {goog.editor.Link} The link being edited.264* @protected265*/266goog.editor.plugins.LinkDialogPlugin.prototype.getCurrentLink = function() {267return this.currentLink_;268};269270271/**272* Creates a new instance of the dialog and registers for the relevant events.273* @param {goog.dom.DomHelper} dialogDomHelper The dom helper to be used to274* create the dialog.275* @param {*=} opt_link The target link (should be a goog.editor.Link).276* @return {!goog.ui.editor.LinkDialog} The dialog.277* @override278* @protected279*/280goog.editor.plugins.LinkDialogPlugin.prototype.createDialog = function(281dialogDomHelper, opt_link) {282var dialog = new goog.ui.editor.LinkDialog(283dialogDomHelper,284/** @type {goog.editor.Link} */ (opt_link));285if (this.emailWarning_) {286dialog.setEmailWarning(this.emailWarning_);287}288if (this.showOpenLinkInNewWindow_) {289dialog.showOpenLinkInNewWindow(this.isOpenLinkInNewWindowChecked_);290}291if (this.showRelNoFollow_) {292dialog.showRelNoFollow();293}294dialog.setStopReferrerLeaks(this.stopReferrerLeaks_);295this.eventHandler_296.listen(dialog, goog.ui.editor.AbstractDialog.EventType.OK, this.handleOk)297.listen(298dialog, goog.ui.editor.AbstractDialog.EventType.CANCEL,299this.handleCancel_)300.listen(301dialog, goog.ui.editor.LinkDialog.EventType.BEFORE_TEST_LINK,302this.handleBeforeTestLink);303return dialog;304};305306307/** @override */308goog.editor.plugins.LinkDialogPlugin.prototype.disposeInternal = function() {309goog.editor.plugins.LinkDialogPlugin.base(this, 'disposeInternal');310this.eventHandler_.dispose();311};312313314/**315* Handles the OK event from the dialog by updating the link in the field.316* @param {goog.ui.editor.LinkDialog.OkEvent} e OK event object.317* @protected318*/319goog.editor.plugins.LinkDialogPlugin.prototype.handleOk = function(e) {320// We're not restoring the original selection, so clear it out.321this.disposeOriginalSelection();322323this.currentLink_.setTextAndUrl(e.linkText, e.linkUrl);324if (this.showOpenLinkInNewWindow_) {325// Save checkbox state for next time.326this.isOpenLinkInNewWindowChecked_ = e.openInNewWindow;327}328329var anchor = this.currentLink_.getAnchor();330this.touchUpAnchorOnOk_(anchor, e);331var extraAnchors = this.currentLink_.getExtraAnchors();332for (var i = 0; i < extraAnchors.length; ++i) {333extraAnchors[i].href = anchor.href;334this.touchUpAnchorOnOk_(extraAnchors[i], e);335}336337// Place cursor to the right of the modified link.338this.currentLink_.placeCursorRightOf();339340this.getFieldObject().focus();341342this.getFieldObject().dispatchSelectionChangeEvent();343this.getFieldObject().dispatchChange();344345this.eventHandler_.removeAll();346};347348349/**350* Apply the necessary properties to a link upon Ok being clicked in the dialog.351* @param {HTMLAnchorElement} anchor The anchor to set properties on.352* @param {goog.events.Event} e Event object.353* @private354*/355goog.editor.plugins.LinkDialogPlugin.prototype.touchUpAnchorOnOk_ = function(356anchor, e) {357if (this.showOpenLinkInNewWindow_) {358if (e.openInNewWindow) {359anchor.target = '_blank';360} else {361if (anchor.target == '_blank') {362anchor.target = '';363}364// If user didn't indicate to open in a new window but the link already365// had a target other than '_blank', let's leave what they had before.366}367}368369if (this.showRelNoFollow_) {370var alreadyPresent = goog.ui.editor.LinkDialog.hasNoFollow(anchor.rel);371if (alreadyPresent && !e.noFollow) {372anchor.rel = goog.ui.editor.LinkDialog.removeNoFollow(anchor.rel);373} else if (!alreadyPresent && e.noFollow) {374anchor.rel = anchor.rel ? anchor.rel + ' nofollow' : 'nofollow';375}376}377};378379380/**381* Handles the CANCEL event from the dialog by clearing the anchor if needed.382* @param {goog.events.Event} e Event object.383* @private384*/385goog.editor.plugins.LinkDialogPlugin.prototype.handleCancel_ = function(e) {386if (this.currentLink_.isNew()) {387goog.dom.flattenElement(this.currentLink_.getAnchor());388var extraAnchors = this.currentLink_.getExtraAnchors();389for (var i = 0; i < extraAnchors.length; ++i) {390goog.dom.flattenElement(extraAnchors[i]);391}392// Make sure listeners know the anchor was flattened out.393this.getFieldObject().dispatchChange();394}395396this.eventHandler_.removeAll();397};398399400/**401* Handles the BeforeTestLink event fired when the 'test' link is clicked.402* @param {goog.ui.editor.LinkDialog.BeforeTestLinkEvent} e BeforeTestLink event403* object.404* @protected405*/406goog.editor.plugins.LinkDialogPlugin.prototype.handleBeforeTestLink = function(407e) {408if (!this.shouldOpenUrl(e.url)) {409/** @desc Message when the user tries to test (preview) a link, but the410* link cannot be tested. */411var MSG_UNSAFE_LINK = goog.getMsg('This link cannot be tested.');412alert(MSG_UNSAFE_LINK);413e.preventDefault();414}415};416417418/**419* Checks whether the plugin should open the given url in a new window.420* @param {string} url The url to check.421* @return {boolean} If the plugin should open the given url in a new window.422* @protected423*/424goog.editor.plugins.LinkDialogPlugin.prototype.shouldOpenUrl = function(url) {425return !this.blockOpeningUnsafeSchemes_ || this.isSafeSchemeToOpen_(url);426};427428429/**430* Determines whether or not a url has a scheme which is safe to open.431* Schemes like javascript are unsafe due to the possibility of XSS.432* @param {string} url A url.433* @return {boolean} Whether the url has a safe scheme.434* @private435*/436goog.editor.plugins.LinkDialogPlugin.prototype.isSafeSchemeToOpen_ = function(437url) {438var scheme = goog.uri.utils.getScheme(url) || 'http';439return goog.array.contains(this.safeToOpenSchemes_, scheme.toLowerCase());440};441442443