linkdialog.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A dialog for editing/creating a link.
  16. *
  17. * @author robbyw@google.com (Robby Walker)
  18. */
  19. goog.provide('goog.ui.editor.LinkDialog');
  20. goog.provide('goog.ui.editor.LinkDialog.BeforeTestLinkEvent');
  21. goog.provide('goog.ui.editor.LinkDialog.EventType');
  22. goog.provide('goog.ui.editor.LinkDialog.OkEvent');
  23. goog.require('goog.a11y.aria');
  24. goog.require('goog.a11y.aria.State');
  25. goog.require('goog.dom');
  26. goog.require('goog.dom.InputType');
  27. goog.require('goog.dom.TagName');
  28. goog.require('goog.dom.safe');
  29. goog.require('goog.editor.BrowserFeature');
  30. goog.require('goog.editor.Link');
  31. goog.require('goog.editor.focus');
  32. goog.require('goog.editor.node');
  33. goog.require('goog.events.Event');
  34. goog.require('goog.events.EventHandler');
  35. goog.require('goog.events.InputHandler');
  36. goog.require('goog.html.SafeHtml');
  37. goog.require('goog.html.SafeHtmlFormatter');
  38. goog.require('goog.string');
  39. goog.require('goog.string.Unicode');
  40. goog.require('goog.style');
  41. goog.require('goog.ui.Button');
  42. goog.require('goog.ui.Component');
  43. goog.require('goog.ui.LinkButtonRenderer');
  44. goog.require('goog.ui.editor.AbstractDialog');
  45. goog.require('goog.ui.editor.TabPane');
  46. goog.require('goog.ui.editor.messages');
  47. goog.require('goog.userAgent');
  48. goog.require('goog.window');
  49. /**
  50. * A type of goog.ui.editor.AbstractDialog for editing/creating a link.
  51. * @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
  52. * dialog's dom structure.
  53. * @param {goog.editor.Link} link The target link.
  54. * @constructor
  55. * @extends {goog.ui.editor.AbstractDialog}
  56. * @final
  57. */
  58. goog.ui.editor.LinkDialog = function(domHelper, link) {
  59. goog.ui.editor.LinkDialog.base(this, 'constructor', domHelper);
  60. /**
  61. * The link being modified by this dialog.
  62. * @type {goog.editor.Link}
  63. * @private
  64. */
  65. this.targetLink_ = link;
  66. /**
  67. * The event handler for this dialog.
  68. * @type {goog.events.EventHandler<!goog.ui.editor.LinkDialog>}
  69. * @private
  70. */
  71. this.eventHandler_ = new goog.events.EventHandler(this);
  72. this.registerDisposable(this.eventHandler_);
  73. /**
  74. * Optional warning to show about email addresses.
  75. * @type {goog.html.SafeHtml}
  76. * @private
  77. */
  78. this.emailWarning_ = null;
  79. /**
  80. * Whether to show a checkbox where the user can choose to have the link open
  81. * in a new window.
  82. * @type {boolean}
  83. * @private
  84. */
  85. this.showOpenLinkInNewWindow_ = false;
  86. /**
  87. * Whether the "open link in new window" checkbox should be checked when the
  88. * dialog is shown, and also whether it was checked last time the dialog was
  89. * closed.
  90. * @type {boolean}
  91. * @private
  92. */
  93. this.isOpenLinkInNewWindowChecked_ = false;
  94. /**
  95. * Whether to show a checkbox where the user can choose to have 'rel=nofollow'
  96. * attribute added to the link.
  97. * @type {boolean}
  98. * @private
  99. */
  100. this.showRelNoFollow_ = false;
  101. /**
  102. * InputHandler object to listen for changes in the url input field.
  103. * @type {goog.events.InputHandler}
  104. * @private
  105. */
  106. this.urlInputHandler_ = null;
  107. /**
  108. * InputHandler object to listen for changes in the email input field.
  109. * @type {goog.events.InputHandler}
  110. * @private
  111. */
  112. this.emailInputHandler_ = null;
  113. /**
  114. * InputHandler object to listen for changes in the text to display input
  115. * field.
  116. * @type {goog.events.InputHandler}
  117. * @private
  118. */
  119. this.textInputHandler_ = null;
  120. /**
  121. * The tab bar where the url and email tabs are.
  122. * @type {goog.ui.editor.TabPane}
  123. * @private
  124. */
  125. this.tabPane_ = null;
  126. /**
  127. * The div element holding the link's display text input.
  128. * @type {HTMLDivElement}
  129. * @private
  130. */
  131. this.textToDisplayDiv_ = null;
  132. /**
  133. * The input element holding the link's display text.
  134. * @type {HTMLInputElement}
  135. * @private
  136. */
  137. this.textToDisplayInput_ = null;
  138. /**
  139. * Whether or not the feature of automatically generating the display text is
  140. * enabled.
  141. * @type {boolean}
  142. * @private
  143. */
  144. this.autogenFeatureEnabled_ = true;
  145. /**
  146. * Whether or not we should automatically generate the display text.
  147. * @type {boolean}
  148. * @private
  149. */
  150. this.autogenerateTextToDisplay_ = false;
  151. /**
  152. * Whether or not automatic generation of the display text is disabled.
  153. * @type {boolean}
  154. * @private
  155. */
  156. this.disableAutogen_ = false;
  157. /**
  158. * The input element (checkbox) to indicate that the link should open in a new
  159. * window.
  160. * @type {HTMLInputElement}
  161. * @private
  162. */
  163. this.openInNewWindowCheckbox_ = null;
  164. /**
  165. * The input element (checkbox) to indicate that the link should have
  166. * 'rel=nofollow' attribute.
  167. * @type {HTMLInputElement}
  168. * @private
  169. */
  170. this.relNoFollowCheckbox_ = null;
  171. /**
  172. * Whether to stop leaking the page's url via the referrer header when the
  173. * "test this link" link is clicked.
  174. * @type {boolean}
  175. * @private
  176. */
  177. this.stopReferrerLeaks_ = false;
  178. };
  179. goog.inherits(goog.ui.editor.LinkDialog, goog.ui.editor.AbstractDialog);
  180. /**
  181. * Events specific to the link dialog.
  182. * @enum {string}
  183. */
  184. goog.ui.editor.LinkDialog.EventType = {
  185. BEFORE_TEST_LINK: 'beforetestlink'
  186. };
  187. /**
  188. * OK event object for the link dialog.
  189. * @param {string} linkText Text the user chose to display for the link.
  190. * @param {string} linkUrl Url the user chose for the link to point to.
  191. * @param {boolean} openInNewWindow Whether the link should open in a new window
  192. * when clicked.
  193. * @param {boolean} noFollow Whether the link should have 'rel=nofollow'
  194. * attribute.
  195. * @constructor
  196. * @extends {goog.events.Event}
  197. * @final
  198. */
  199. goog.ui.editor.LinkDialog.OkEvent = function(
  200. linkText, linkUrl, openInNewWindow, noFollow) {
  201. goog.ui.editor.LinkDialog.OkEvent.base(
  202. this, 'constructor', goog.ui.editor.AbstractDialog.EventType.OK);
  203. /**
  204. * The text of the link edited in the dialog.
  205. * @type {string}
  206. */
  207. this.linkText = linkText;
  208. /**
  209. * The url of the link edited in the dialog.
  210. * @type {string}
  211. */
  212. this.linkUrl = linkUrl;
  213. /**
  214. * Whether the link should open in a new window when clicked.
  215. * @type {boolean}
  216. */
  217. this.openInNewWindow = openInNewWindow;
  218. /**
  219. * Whether the link should have 'rel=nofollow' attribute.
  220. * @type {boolean}
  221. */
  222. this.noFollow = noFollow;
  223. };
  224. goog.inherits(goog.ui.editor.LinkDialog.OkEvent, goog.events.Event);
  225. /**
  226. * Event fired before testing a link by opening it in another window.
  227. * Calling preventDefault will stop the link from being opened.
  228. * @param {string} url Url of the link being tested.
  229. * @constructor
  230. * @extends {goog.events.Event}
  231. * @final
  232. */
  233. goog.ui.editor.LinkDialog.BeforeTestLinkEvent = function(url) {
  234. goog.ui.editor.LinkDialog.BeforeTestLinkEvent.base(
  235. this, 'constructor',
  236. goog.ui.editor.LinkDialog.EventType.BEFORE_TEST_LINK);
  237. /**
  238. * The url of the link being tested.
  239. * @type {string}
  240. */
  241. this.url = url;
  242. };
  243. goog.inherits(goog.ui.editor.LinkDialog.BeforeTestLinkEvent, goog.events.Event);
  244. /**
  245. * Sets the warning message to show to users about including email addresses on
  246. * public web pages.
  247. * @param {!goog.html.SafeHtml} emailWarning Warning message to show users about
  248. * including email addresses on the web.
  249. */
  250. goog.ui.editor.LinkDialog.prototype.setEmailWarning = function(emailWarning) {
  251. this.emailWarning_ = emailWarning;
  252. };
  253. /**
  254. * Tells the dialog to show a checkbox where the user can choose to have the
  255. * link open in a new window.
  256. * @param {boolean} startChecked Whether to check the checkbox the first
  257. * time the dialog is shown. Subesquent times the checkbox will remember its
  258. * previous state.
  259. */
  260. goog.ui.editor.LinkDialog.prototype.showOpenLinkInNewWindow = function(
  261. startChecked) {
  262. this.showOpenLinkInNewWindow_ = true;
  263. this.isOpenLinkInNewWindowChecked_ = startChecked;
  264. };
  265. /**
  266. * Tells the dialog to show a checkbox where the user can choose to add
  267. * 'rel=nofollow' attribute to the link.
  268. */
  269. goog.ui.editor.LinkDialog.prototype.showRelNoFollow = function() {
  270. this.showRelNoFollow_ = true;
  271. };
  272. /** @override */
  273. goog.ui.editor.LinkDialog.prototype.show = function() {
  274. goog.ui.editor.LinkDialog.base(this, 'show');
  275. this.selectAppropriateTab_(
  276. this.textToDisplayInput_.value, this.getTargetUrl_());
  277. this.syncOkButton_();
  278. if (this.showOpenLinkInNewWindow_) {
  279. if (!this.targetLink_.isNew()) {
  280. // If link is not new, checkbox should reflect current target.
  281. this.isOpenLinkInNewWindowChecked_ =
  282. this.targetLink_.getAnchor().target == '_blank';
  283. }
  284. this.openInNewWindowCheckbox_.checked = this.isOpenLinkInNewWindowChecked_;
  285. }
  286. if (this.showRelNoFollow_) {
  287. this.relNoFollowCheckbox_.checked =
  288. goog.ui.editor.LinkDialog.hasNoFollow(this.targetLink_.getAnchor().rel);
  289. }
  290. };
  291. /** @override */
  292. goog.ui.editor.LinkDialog.prototype.hide = function() {
  293. this.disableAutogenFlag_(false);
  294. goog.ui.editor.LinkDialog.base(this, 'hide');
  295. };
  296. /**
  297. * Tells the dialog whether to show the 'text to display' div.
  298. * When the target element of the dialog is an image, there is no link text
  299. * to modify. This function can be used for this kind of situations.
  300. * @param {boolean} visible Whether to make 'text to display' div visible.
  301. */
  302. goog.ui.editor.LinkDialog.prototype.setTextToDisplayVisible = function(
  303. visible) {
  304. if (this.textToDisplayDiv_) {
  305. goog.style.setStyle(
  306. this.textToDisplayDiv_, 'display', visible ? 'block' : 'none');
  307. }
  308. };
  309. /**
  310. * Tells the plugin whether to stop leaking the page's url via the referrer
  311. * header when the "test this link" link is clicked.
  312. * @param {boolean} stop Whether to stop leaking the referrer.
  313. */
  314. goog.ui.editor.LinkDialog.prototype.setStopReferrerLeaks = function(stop) {
  315. this.stopReferrerLeaks_ = stop;
  316. };
  317. /**
  318. * Tells the dialog whether the autogeneration of text to display is to be
  319. * enabled.
  320. * @param {boolean} enable Whether to enable the feature.
  321. */
  322. goog.ui.editor.LinkDialog.prototype.setAutogenFeatureEnabled = function(
  323. enable) {
  324. this.autogenFeatureEnabled_ = enable;
  325. };
  326. /**
  327. * Checks if {@code str} contains {@code "nofollow"} as a separate word.
  328. * @param {string} str String to be tested. This is usually {@code rel}
  329. * attribute of an {@code HTMLAnchorElement} object.
  330. * @return {boolean} {@code true} if {@code str} contains {@code nofollow}.
  331. */
  332. goog.ui.editor.LinkDialog.hasNoFollow = function(str) {
  333. return goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_.test(str);
  334. };
  335. /**
  336. * Removes {@code "nofollow"} from {@code rel} if it's present as a separate
  337. * word.
  338. * @param {string} rel Input string. This is usually {@code rel} attribute of
  339. * an {@code HTMLAnchorElement} object.
  340. * @return {string} {@code rel} with any {@code "nofollow"} removed.
  341. */
  342. goog.ui.editor.LinkDialog.removeNoFollow = function(rel) {
  343. return rel.replace(goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_, '');
  344. };
  345. // *** Protected interface ************************************************** //
  346. /** @override */
  347. goog.ui.editor.LinkDialog.prototype.createDialogControl = function() {
  348. var builder = new goog.ui.editor.AbstractDialog.Builder(this);
  349. builder.setTitle(goog.ui.editor.messages.MSG_EDIT_LINK)
  350. .setContent(this.createDialogContent_());
  351. return builder.build();
  352. };
  353. /**
  354. * Creates and returns the event object to be used when dispatching the OK
  355. * event to listeners based on which tab is currently selected and the contents
  356. * of the input fields of that tab.
  357. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when
  358. * dispatching the OK event to listeners.
  359. * @protected
  360. * @override
  361. */
  362. goog.ui.editor.LinkDialog.prototype.createOkEvent = function() {
  363. if (this.tabPane_.getCurrentTabId() ==
  364. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB) {
  365. return this.createOkEventFromEmailTab_();
  366. } else {
  367. return this.createOkEventFromWebTab_();
  368. }
  369. };
  370. // *** Private implementation *********************************************** //
  371. /**
  372. * Regular expression that matches {@code nofollow} value in an
  373. * {@code * HTMLAnchorElement}'s {@code rel} element.
  374. * @type {RegExp}
  375. * @private
  376. */
  377. goog.ui.editor.LinkDialog.NO_FOLLOW_REGEX_ = /\bnofollow\b/i;
  378. /**
  379. * Creates contents of this dialog.
  380. * @return {!Element} Contents of the dialog as a DOM element.
  381. * @private
  382. */
  383. goog.ui.editor.LinkDialog.prototype.createDialogContent_ = function() {
  384. this.textToDisplayDiv_ =
  385. /** @type {!HTMLDivElement} */ (this.buildTextToDisplayDiv_());
  386. var content =
  387. this.dom.createDom(goog.dom.TagName.DIV, null, this.textToDisplayDiv_);
  388. this.tabPane_ =
  389. new goog.ui.editor.TabPane(this.dom, goog.ui.editor.messages.MSG_LINK_TO);
  390. this.registerDisposable(this.tabPane_);
  391. this.tabPane_.addTab(
  392. goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB,
  393. goog.ui.editor.messages.MSG_ON_THE_WEB,
  394. goog.ui.editor.messages.MSG_ON_THE_WEB_TIP,
  395. goog.ui.editor.LinkDialog.BUTTON_GROUP_, this.buildTabOnTheWeb_());
  396. this.tabPane_.addTab(
  397. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB,
  398. goog.ui.editor.messages.MSG_EMAIL_ADDRESS,
  399. goog.ui.editor.messages.MSG_EMAIL_ADDRESS_TIP,
  400. goog.ui.editor.LinkDialog.BUTTON_GROUP_, this.buildTabEmailAddress_());
  401. this.tabPane_.render(content);
  402. this.eventHandler_.listen(
  403. this.tabPane_, goog.ui.Component.EventType.SELECT, this.onChangeTab_);
  404. if (this.showOpenLinkInNewWindow_) {
  405. content.appendChild(this.buildOpenInNewWindowDiv_());
  406. }
  407. if (this.showRelNoFollow_) {
  408. content.appendChild(this.buildRelNoFollowDiv_());
  409. }
  410. return content;
  411. };
  412. /**
  413. * Builds and returns the text to display section of the edit link dialog.
  414. * @return {!Element} A div element to be appended into the dialog div.
  415. * @private
  416. */
  417. goog.ui.editor.LinkDialog.prototype.buildTextToDisplayDiv_ = function() {
  418. var table = this.dom.createTable(1, 2);
  419. table.cellSpacing = '0';
  420. table.cellPadding = '0';
  421. table.style.fontSize = '10pt';
  422. // Build the text to display input.
  423. var textToDisplayDiv = this.dom.createDom(goog.dom.TagName.DIV);
  424. var html = goog.html.SafeHtml.create(
  425. 'span', {
  426. 'style': {
  427. 'position': 'relative',
  428. 'bottom': '2px',
  429. 'padding-right': '1px',
  430. 'white-space': 'nowrap'
  431. },
  432. id: goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY_LABEL
  433. },
  434. [goog.ui.editor.messages.MSG_TEXT_TO_DISPLAY, goog.string.Unicode.NBSP]);
  435. goog.dom.safe.setInnerHtml(table.rows[0].cells[0], html);
  436. this.textToDisplayInput_ = this.dom.createDom(
  437. goog.dom.TagName.INPUT,
  438. {id: goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY});
  439. var textInput = this.textToDisplayInput_;
  440. // 98% prevents scroll bars in standards mode.
  441. // TODO(robbyw): Is this necessary for quirks mode?
  442. goog.style.setStyle(textInput, 'width', '98%');
  443. goog.style.setStyle(table.rows[0].cells[1], 'width', '100%');
  444. goog.dom.appendChild(table.rows[0].cells[1], textInput);
  445. goog.a11y.aria.setState(
  446. /** @type {!Element} */ (textInput), goog.a11y.aria.State.LABELLEDBY,
  447. goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY_LABEL);
  448. textInput.value = this.targetLink_.getCurrentText();
  449. this.textInputHandler_ = new goog.events.InputHandler(textInput);
  450. this.registerDisposable(this.textInputHandler_);
  451. this.eventHandler_.listen(
  452. this.textInputHandler_, goog.events.InputHandler.EventType.INPUT,
  453. this.onTextToDisplayEdit_);
  454. goog.dom.appendChild(textToDisplayDiv, table);
  455. return textToDisplayDiv;
  456. };
  457. /**
  458. * Builds and returns the "checkbox to open the link in a new window" section of
  459. * the edit link dialog.
  460. * @return {!Element} A div element to be appended into the dialog div.
  461. * @private
  462. */
  463. goog.ui.editor.LinkDialog.prototype.buildOpenInNewWindowDiv_ = function() {
  464. this.openInNewWindowCheckbox_ = this.dom.createDom(
  465. goog.dom.TagName.INPUT, {'type': goog.dom.InputType.CHECKBOX});
  466. return this.dom.createDom(
  467. goog.dom.TagName.DIV, null,
  468. this.dom.createDom(
  469. goog.dom.TagName.LABEL, null, this.openInNewWindowCheckbox_,
  470. goog.ui.editor.messages.MSG_OPEN_IN_NEW_WINDOW));
  471. };
  472. /**
  473. * Creates a DIV with a checkbox for {@code rel=nofollow} option.
  474. * @return {!Element} Newly created DIV element.
  475. * @private
  476. */
  477. goog.ui.editor.LinkDialog.prototype.buildRelNoFollowDiv_ = function() {
  478. var formatter = new goog.html.SafeHtmlFormatter();
  479. /** @desc Checkbox text for adding 'rel=nofollow' attribute to a link. */
  480. var MSG_ADD_REL_NOFOLLOW_ATTR = goog.getMsg(
  481. "Add '{$relNoFollow}' attribute ({$linkStart}Learn more{$linkEnd})", {
  482. 'relNoFollow': 'rel=nofollow',
  483. 'linkStart': formatter.startTag('a', {
  484. 'href': 'http://support.google.com/webmasters/bin/' +
  485. 'answer.py?hl=en&answer=96569',
  486. 'target': '_blank'
  487. }),
  488. 'linkEnd': formatter.endTag('a')
  489. });
  490. this.relNoFollowCheckbox_ = this.dom.createDom(
  491. goog.dom.TagName.INPUT, {'type': goog.dom.InputType.CHECKBOX});
  492. return this.dom.createDom(
  493. goog.dom.TagName.DIV, null,
  494. this.dom.createDom(
  495. goog.dom.TagName.LABEL, null, this.relNoFollowCheckbox_,
  496. goog.dom.safeHtmlToNode(
  497. formatter.format(MSG_ADD_REL_NOFOLLOW_ATTR))));
  498. };
  499. /**
  500. * Builds and returns the div containing the tab "On the web".
  501. * @return {!Element} The div element containing the tab.
  502. * @private
  503. */
  504. goog.ui.editor.LinkDialog.prototype.buildTabOnTheWeb_ = function() {
  505. var onTheWebDiv = this.dom.createElement(goog.dom.TagName.DIV);
  506. var headingDiv = this.dom.createDom(
  507. goog.dom.TagName.DIV, {},
  508. this.dom.createDom(
  509. goog.dom.TagName.B, {}, goog.ui.editor.messages.MSG_WHAT_URL));
  510. var urlInput = this.dom.createDom(goog.dom.TagName.INPUT, {
  511. id: goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT,
  512. className: goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_
  513. });
  514. goog.a11y.aria.setState(
  515. urlInput, goog.a11y.aria.State.LABELLEDBY,
  516. goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
  517. // IE throws on unknown values for type, but IE10+ supports type=url
  518. if (!goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10')) {
  519. // On browsers that support Web Forms 2.0, allow autocompletion of URLs.
  520. urlInput.type = goog.dom.InputType.URL;
  521. }
  522. if (goog.editor.BrowserFeature.NEEDS_99_WIDTH_IN_STANDARDS_MODE &&
  523. goog.editor.node.isStandardsMode(urlInput)) {
  524. urlInput.style.width = '99%';
  525. }
  526. var inputDiv = this.dom.createDom(goog.dom.TagName.DIV, null, urlInput);
  527. this.urlInputHandler_ = new goog.events.InputHandler(urlInput);
  528. this.registerDisposable(this.urlInputHandler_);
  529. this.eventHandler_.listen(
  530. this.urlInputHandler_, goog.events.InputHandler.EventType.INPUT,
  531. this.onUrlOrEmailInputChange_);
  532. var testLink = new goog.ui.Button(
  533. goog.ui.editor.messages.MSG_TEST_THIS_LINK,
  534. goog.ui.LinkButtonRenderer.getInstance(), this.dom);
  535. testLink.render(inputDiv);
  536. testLink.getElement().style.marginTop = '1em';
  537. this.eventHandler_.listen(
  538. testLink, goog.ui.Component.EventType.ACTION, this.onWebTestLink_);
  539. // Build the "On the web" explanation text div.
  540. var explanationDiv = this.dom.createDom(
  541. goog.dom.TagName.DIV,
  542. goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_);
  543. goog.dom.safe.setInnerHtml(
  544. explanationDiv, goog.ui.editor.messages.getTrLinkExplanationSafeHtml());
  545. onTheWebDiv.appendChild(headingDiv);
  546. onTheWebDiv.appendChild(inputDiv);
  547. onTheWebDiv.appendChild(explanationDiv);
  548. return onTheWebDiv;
  549. };
  550. /**
  551. * Builds and returns the div containing the tab "Email address".
  552. * @return {!Element} the div element containing the tab.
  553. * @private
  554. */
  555. goog.ui.editor.LinkDialog.prototype.buildTabEmailAddress_ = function() {
  556. var emailTab = this.dom.createDom(goog.dom.TagName.DIV);
  557. var headingDiv = this.dom.createDom(
  558. goog.dom.TagName.DIV, {},
  559. this.dom.createDom(
  560. goog.dom.TagName.B, {}, goog.ui.editor.messages.MSG_WHAT_EMAIL));
  561. goog.dom.appendChild(emailTab, headingDiv);
  562. var emailInput = this.dom.createDom(goog.dom.TagName.INPUT, {
  563. id: goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT,
  564. className: goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_
  565. });
  566. goog.a11y.aria.setState(
  567. emailInput, goog.a11y.aria.State.LABELLEDBY,
  568. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB);
  569. if (goog.editor.BrowserFeature.NEEDS_99_WIDTH_IN_STANDARDS_MODE &&
  570. goog.editor.node.isStandardsMode(emailInput)) {
  571. // Standards mode sizes this too large.
  572. emailInput.style.width = '99%';
  573. }
  574. goog.dom.appendChild(emailTab, emailInput);
  575. this.emailInputHandler_ = new goog.events.InputHandler(emailInput);
  576. this.registerDisposable(this.emailInputHandler_);
  577. this.eventHandler_.listen(
  578. this.emailInputHandler_, goog.events.InputHandler.EventType.INPUT,
  579. this.onUrlOrEmailInputChange_);
  580. goog.dom.appendChild(
  581. emailTab,
  582. this.dom.createDom(
  583. goog.dom.TagName.DIV, {
  584. id: goog.ui.editor.LinkDialog.Id_.EMAIL_WARNING,
  585. className: goog.ui.editor.LinkDialog.EMAIL_WARNING_CLASSNAME_,
  586. style: 'visibility:hidden'
  587. },
  588. goog.ui.editor.messages.MSG_INVALID_EMAIL));
  589. if (this.emailWarning_) {
  590. var explanationDiv = this.dom.createDom(
  591. goog.dom.TagName.DIV,
  592. goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_);
  593. goog.dom.safe.setInnerHtml(explanationDiv, this.emailWarning_);
  594. goog.dom.appendChild(emailTab, explanationDiv);
  595. }
  596. return emailTab;
  597. };
  598. /**
  599. * Returns the url that the target points to.
  600. * @return {string} The url that the target points to.
  601. * @private
  602. */
  603. goog.ui.editor.LinkDialog.prototype.getTargetUrl_ = function() {
  604. // Get the href-attribute through getAttribute() rather than the href property
  605. // because Google-Toolbar on Firefox with "Send with Gmail" turned on
  606. // modifies the href-property of 'mailto:' links but leaves the attribute
  607. // untouched.
  608. return this.targetLink_.getAnchor().getAttribute('href') || '';
  609. };
  610. /**
  611. * Selects the correct tab based on the URL, and fills in its inputs.
  612. * For new links, it suggests a url based on the link text.
  613. * @param {string} text The inner text of the link.
  614. * @param {string} url The href for the link.
  615. * @private
  616. */
  617. goog.ui.editor.LinkDialog.prototype.selectAppropriateTab_ = function(
  618. text, url) {
  619. if (this.isNewLink_()) {
  620. // Newly created non-empty link: try to infer URL from the link text.
  621. this.guessUrlAndSelectTab_(text);
  622. } else if (goog.editor.Link.isMailto(url)) {
  623. // The link is for an email.
  624. this.tabPane_.setSelectedTabId(
  625. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB);
  626. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT)
  627. .value = url.substring(url.indexOf(':') + 1);
  628. this.setAutogenFlagFromCurInput_();
  629. } else {
  630. // No specific tab was appropriate, default to on the web tab.
  631. this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
  632. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value =
  633. this.isNewLink_() ? 'http://' : url;
  634. this.setAutogenFlagFromCurInput_();
  635. }
  636. };
  637. /**
  638. * Select a url/tab based on the link's text. This function is simply
  639. * the isNewLink_() == true case of selectAppropriateTab_().
  640. * @param {string} text The inner text of the link.
  641. * @private
  642. */
  643. goog.ui.editor.LinkDialog.prototype.guessUrlAndSelectTab_ = function(text) {
  644. if (goog.editor.Link.isLikelyEmailAddress(text)) {
  645. // The text is for an email address.
  646. this.tabPane_.setSelectedTabId(
  647. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB);
  648. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT)
  649. .value = text;
  650. this.setAutogenFlag_(true);
  651. // TODO(user): Why disable right after enabling? What bug are we
  652. // working around?
  653. this.disableAutogenFlag_(true);
  654. } else if (goog.editor.Link.isLikelyUrl(text)) {
  655. // The text is for a web URL.
  656. this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
  657. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value =
  658. text;
  659. this.setAutogenFlag_(true);
  660. this.disableAutogenFlag_(true);
  661. } else {
  662. // No meaning could be deduced from text, choose a default tab.
  663. if (!this.targetLink_.getCurrentText()) {
  664. this.setAutogenFlag_(true);
  665. }
  666. this.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
  667. }
  668. };
  669. /**
  670. * Called on a change to the url or email input. If either one of those tabs
  671. * is active, sets the OK button to enabled/disabled accordingly.
  672. * @private
  673. */
  674. goog.ui.editor.LinkDialog.prototype.syncOkButton_ = function() {
  675. var inputValue;
  676. if (this.tabPane_.getCurrentTabId() ==
  677. goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB) {
  678. inputValue =
  679. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT)
  680. .value;
  681. this.toggleInvalidEmailWarning_(
  682. inputValue != '' && !goog.editor.Link.isLikelyEmailAddress(inputValue));
  683. } else if (
  684. this.tabPane_.getCurrentTabId() ==
  685. goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB) {
  686. inputValue =
  687. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT).value;
  688. } else {
  689. return;
  690. }
  691. this.getOkButtonElement().disabled =
  692. goog.string.isEmptyOrWhitespace(inputValue);
  693. };
  694. /**
  695. * Show/hide the Invalid Email Address warning.
  696. * @param {boolean} on Whether to show the warning.
  697. * @private
  698. */
  699. goog.ui.editor.LinkDialog.prototype.toggleInvalidEmailWarning_ = function(on) {
  700. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.EMAIL_WARNING)
  701. .style.visibility = (on ? 'visible' : 'hidden');
  702. };
  703. /**
  704. * Changes the autogenerateTextToDisplay flag so that text to
  705. * display stops autogenerating.
  706. * @private
  707. */
  708. goog.ui.editor.LinkDialog.prototype.onTextToDisplayEdit_ = function() {
  709. var inputEmpty = this.textToDisplayInput_.value == '';
  710. if (inputEmpty) {
  711. this.setAutogenFlag_(true);
  712. } else {
  713. this.setAutogenFlagFromCurInput_();
  714. }
  715. };
  716. /**
  717. * The function called when hitting OK with the "On the web" tab current.
  718. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when
  719. * dispatching the OK event to listeners.
  720. * @private
  721. */
  722. goog.ui.editor.LinkDialog.prototype.createOkEventFromWebTab_ = function() {
  723. var input = /** @type {HTMLInputElement} */ (
  724. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT));
  725. var linkURL = input.value;
  726. if (goog.editor.Link.isLikelyEmailAddress(linkURL)) {
  727. // Make sure that if user types in an e-mail address, it becomes "mailto:".
  728. return this.createOkEventFromEmailTab_(
  729. goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT);
  730. } else {
  731. if (linkURL.search(/:/) < 0) {
  732. linkURL = 'http://' + goog.string.trimLeft(linkURL);
  733. }
  734. return this.createOkEventFromUrl_(linkURL);
  735. }
  736. };
  737. /**
  738. * The function called when hitting OK with the "email address" tab current.
  739. * @param {string=} opt_inputId Id of an alternate input to check.
  740. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when
  741. * dispatching the OK event to listeners.
  742. * @private
  743. */
  744. goog.ui.editor.LinkDialog.prototype.createOkEventFromEmailTab_ = function(
  745. opt_inputId) {
  746. var linkURL =
  747. this.dom
  748. .getElement(
  749. opt_inputId || goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT)
  750. .value;
  751. linkURL = 'mailto:' + linkURL;
  752. return this.createOkEventFromUrl_(linkURL);
  753. };
  754. /**
  755. * Function to test a link from the on the web tab.
  756. * @private
  757. */
  758. goog.ui.editor.LinkDialog.prototype.onWebTestLink_ = function() {
  759. var input = /** @type {HTMLInputElement} */ (
  760. this.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT));
  761. var url = input.value;
  762. if (url.search(/:/) < 0) {
  763. url = 'http://' + goog.string.trimLeft(url);
  764. }
  765. if (this.dispatchEvent(
  766. new goog.ui.editor.LinkDialog.BeforeTestLinkEvent(url))) {
  767. var win = this.dom.getWindow();
  768. var size = goog.dom.getViewportSize(win);
  769. var openOptions = {
  770. target: '_blank',
  771. width: Math.max(size.width - 50, 50),
  772. height: Math.max(size.height - 50, 50),
  773. toolbar: true,
  774. scrollbars: true,
  775. location: true,
  776. statusbar: false,
  777. menubar: true, 'resizable': true, 'noreferrer': this.stopReferrerLeaks_
  778. };
  779. goog.window.open(url, openOptions, win);
  780. }
  781. };
  782. /**
  783. * Called whenever the url or email input is edited. If the text to display
  784. * matches the text to display, turn on auto. Otherwise if auto is on, update
  785. * the text to display based on the url.
  786. * @private
  787. */
  788. goog.ui.editor.LinkDialog.prototype.onUrlOrEmailInputChange_ = function() {
  789. if (this.autogenerateTextToDisplay_) {
  790. this.setTextToDisplayFromAuto_();
  791. } else if (this.textToDisplayInput_.value == '') {
  792. this.setAutogenFlagFromCurInput_();
  793. }
  794. this.syncOkButton_();
  795. };
  796. /**
  797. * Called when the currently selected tab changes.
  798. * @param {goog.events.Event} e The tab change event.
  799. * @private
  800. */
  801. goog.ui.editor.LinkDialog.prototype.onChangeTab_ = function(e) {
  802. var tab = /** @type {goog.ui.Tab} */ (e.target);
  803. // Focus on the input field in the selected tab.
  804. var input = /** @type {!HTMLElement} */ (
  805. this.dom.getElement(
  806. tab.getId() + goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX));
  807. goog.editor.focus.focusInputField(input);
  808. // For some reason, IE does not fire onpropertychange events when the width
  809. // is specified as a percentage, which breaks the InputHandlers.
  810. input.style.width = '';
  811. input.style.width = input.offsetWidth + 'px';
  812. this.syncOkButton_();
  813. this.setTextToDisplayFromAuto_();
  814. };
  815. /**
  816. * If autogen is turned on, set the value of text to display based on the
  817. * current selection or url.
  818. * @private
  819. */
  820. goog.ui.editor.LinkDialog.prototype.setTextToDisplayFromAuto_ = function() {
  821. if (this.autogenFeatureEnabled_ && this.autogenerateTextToDisplay_) {
  822. var inputId = this.tabPane_.getCurrentTabId() +
  823. goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX;
  824. this.textToDisplayInput_.value =
  825. /** @type {HTMLInputElement} */ (this.dom.getElement(inputId)).value;
  826. }
  827. };
  828. /**
  829. * Turn on the autogenerate text to display flag, and set some sort of indicator
  830. * that autogen is on.
  831. * @param {boolean} val Boolean value to set autogenerate to.
  832. * @private
  833. */
  834. goog.ui.editor.LinkDialog.prototype.setAutogenFlag_ = function(val) {
  835. // TODO(user): This whole autogen thing is very confusing. It needs
  836. // to be refactored and/or explained.
  837. this.autogenerateTextToDisplay_ = val;
  838. };
  839. /**
  840. * Disables autogen so that onUrlOrEmailInputChange_ doesn't act in cases
  841. * that are undesirable.
  842. * @param {boolean} autogen Boolean value to set disableAutogen to.
  843. * @private
  844. */
  845. goog.ui.editor.LinkDialog.prototype.disableAutogenFlag_ = function(autogen) {
  846. this.setAutogenFlag_(!autogen);
  847. this.disableAutogen_ = autogen;
  848. };
  849. /**
  850. * Creates an OK event from the text to display input and the specified link.
  851. * If text to display input is empty, then generate the auto value for it.
  852. * @return {!goog.ui.editor.LinkDialog.OkEvent} The event object to be used when
  853. * dispatching the OK event to listeners.
  854. * @param {string} url Url the target element should point to.
  855. * @private
  856. */
  857. goog.ui.editor.LinkDialog.prototype.createOkEventFromUrl_ = function(url) {
  858. // Fill in the text to display input in case it is empty.
  859. this.setTextToDisplayFromAuto_();
  860. if (this.showOpenLinkInNewWindow_) {
  861. // Save checkbox state for next time.
  862. this.isOpenLinkInNewWindowChecked_ = this.openInNewWindowCheckbox_.checked;
  863. }
  864. return new goog.ui.editor.LinkDialog.OkEvent(
  865. this.textToDisplayInput_.value, url,
  866. this.showOpenLinkInNewWindow_ && this.isOpenLinkInNewWindowChecked_,
  867. this.showRelNoFollow_ && this.relNoFollowCheckbox_.checked);
  868. };
  869. /**
  870. * If an email or url is being edited, set autogenerate to on if the text to
  871. * display matches the url.
  872. * @private
  873. */
  874. goog.ui.editor.LinkDialog.prototype.setAutogenFlagFromCurInput_ = function() {
  875. var autogen = false;
  876. if (!this.disableAutogen_) {
  877. var tabInput = this.dom.getElement(
  878. this.tabPane_.getCurrentTabId() +
  879. goog.ui.editor.LinkDialog.Id_.TAB_INPUT_SUFFIX);
  880. autogen = (tabInput.value == this.textToDisplayInput_.value);
  881. }
  882. this.setAutogenFlag_(autogen);
  883. };
  884. /**
  885. * @return {boolean} Whether the link is new.
  886. * @private
  887. */
  888. goog.ui.editor.LinkDialog.prototype.isNewLink_ = function() {
  889. return this.targetLink_.isNew();
  890. };
  891. /**
  892. * IDs for relevant DOM elements.
  893. * @enum {string}
  894. * @private
  895. */
  896. goog.ui.editor.LinkDialog.Id_ = {
  897. TEXT_TO_DISPLAY: 'linkdialog-text',
  898. TEXT_TO_DISPLAY_LABEL: 'linkdialog-text-label',
  899. ON_WEB_TAB: 'linkdialog-onweb',
  900. ON_WEB_INPUT: 'linkdialog-onweb-tab-input',
  901. EMAIL_ADDRESS_TAB: 'linkdialog-email',
  902. EMAIL_ADDRESS_INPUT: 'linkdialog-email-tab-input',
  903. EMAIL_WARNING: 'linkdialog-email-warning',
  904. TAB_INPUT_SUFFIX: '-tab-input'
  905. };
  906. /**
  907. * Base name for the radio buttons group.
  908. * @type {string}
  909. * @private
  910. */
  911. goog.ui.editor.LinkDialog.BUTTON_GROUP_ = 'linkdialog-buttons';
  912. /**
  913. * Class name for the url and email input elements.
  914. * @type {string}
  915. * @private
  916. */
  917. goog.ui.editor.LinkDialog.TARGET_INPUT_CLASSNAME_ =
  918. goog.getCssName('tr-link-dialog-target-input');
  919. /**
  920. * Class name for the email address warning element.
  921. * @type {string}
  922. * @private
  923. */
  924. goog.ui.editor.LinkDialog.EMAIL_WARNING_CLASSNAME_ =
  925. goog.getCssName('tr-link-dialog-email-warning');
  926. /**
  927. * Class name for the explanation text elements.
  928. * @type {string}
  929. * @private
  930. */
  931. goog.ui.editor.LinkDialog.EXPLANATION_TEXT_CLASSNAME_ =
  932. goog.getCssName('tr-link-dialog-explanation-text');