PageRenderTime 85ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/src/prime/lib/closure-library/goog/editor/plugins/linkbubble.js

https://github.com/ggppwx/heuristic-problem-solving-
JavaScript | 458 lines | 190 code | 81 blank | 187 comment | 21 complexity | 13d7776c20bfc124e1ccf6258e196eef MD5 | raw file
  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 Base class for bubble plugins.
  16. *
  17. */
  18. goog.provide('goog.editor.plugins.LinkBubble');
  19. goog.provide('goog.editor.plugins.LinkBubble.Action');
  20. goog.require('goog.array');
  21. goog.require('goog.dom');
  22. goog.require('goog.editor.BrowserFeature');
  23. goog.require('goog.editor.Command');
  24. goog.require('goog.editor.Link');
  25. goog.require('goog.editor.plugins.AbstractBubblePlugin');
  26. goog.require('goog.editor.range');
  27. goog.require('goog.string');
  28. goog.require('goog.style');
  29. goog.require('goog.ui.editor.messages');
  30. goog.require('goog.window');
  31. /**
  32. * Property bubble plugin for links.
  33. * @param {...!goog.editor.plugins.LinkBubble.Action} var_args List of
  34. * extra actions supported by the bubble.
  35. * @constructor
  36. * @extends {goog.editor.plugins.AbstractBubblePlugin}
  37. */
  38. goog.editor.plugins.LinkBubble = function(var_args) {
  39. goog.base(this);
  40. /**
  41. * List of extra actions supported by the bubble.
  42. * @type {Array.<!goog.editor.plugins.LinkBubble.Action>}
  43. * @private
  44. */
  45. this.extraActions_ = goog.array.toArray(arguments);
  46. /**
  47. * List of spans corresponding to the extra actions.
  48. * @type {Array.<!Element>}
  49. * @private
  50. */
  51. this.actionSpans_ = [];
  52. };
  53. goog.inherits(goog.editor.plugins.LinkBubble,
  54. goog.editor.plugins.AbstractBubblePlugin);
  55. /**
  56. * Element id for the link text.
  57. * type {string}
  58. * @private
  59. */
  60. goog.editor.plugins.LinkBubble.LINK_TEXT_ID_ = 'tr_link-text';
  61. /**
  62. * Element id for the test link span.
  63. * type {string}
  64. * @private
  65. */
  66. goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_ = 'tr_test-link-span';
  67. /**
  68. * Element id for the test link.
  69. * type {string}
  70. * @private
  71. */
  72. goog.editor.plugins.LinkBubble.TEST_LINK_ID_ = 'tr_test-link';
  73. /**
  74. * Element id for the change link span.
  75. * type {string}
  76. * @private
  77. */
  78. goog.editor.plugins.LinkBubble.CHANGE_LINK_SPAN_ID_ = 'tr_change-link-span';
  79. /**
  80. * Element id for the link.
  81. * type {string}
  82. * @private
  83. */
  84. goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_ = 'tr_change-link';
  85. /**
  86. * Element id for the delete link span.
  87. * type {string}
  88. * @private
  89. */
  90. goog.editor.plugins.LinkBubble.DELETE_LINK_SPAN_ID_ = 'tr_delete-link-span';
  91. /**
  92. * Element id for the delete link.
  93. * type {string}
  94. * @private
  95. */
  96. goog.editor.plugins.LinkBubble.DELETE_LINK_ID_ = 'tr_delete-link';
  97. /**
  98. * Element id for the link bubble wrapper div.
  99. * type {string}
  100. * @private
  101. */
  102. goog.editor.plugins.LinkBubble.LINK_DIV_ID_ = 'tr_link-div';
  103. /**
  104. * @desc Text label for link that lets the user click it to see where the link
  105. * this bubble is for point to.
  106. */
  107. var MSG_LINK_BUBBLE_TEST_LINK = goog.getMsg('Go to link: ');
  108. /**
  109. * @desc Label that pops up a dialog to change the link.
  110. */
  111. var MSG_LINK_BUBBLE_CHANGE = goog.getMsg('Change');
  112. /**
  113. * @desc Label that allow the user to remove this link.
  114. */
  115. var MSG_LINK_BUBBLE_REMOVE = goog.getMsg('Remove');
  116. /**
  117. * Whether to stop leaking the page's url via the referrer header when the
  118. * link text link is clicked.
  119. * @type {boolean}
  120. * @private
  121. */
  122. goog.editor.plugins.LinkBubble.prototype.stopReferrerLeaks_ = false;
  123. /**
  124. * Tells the plugin to stop leaking the page's url via the referrer header when
  125. * the link text link is clicked. When the user clicks on a link, the
  126. * browser makes a request for the link url, passing the url of the current page
  127. * in the request headers. If the user wants the current url to be kept secret
  128. * (e.g. an unpublished document), the owner of the url that was clicked will
  129. * see the secret url in the request headers, and it will no longer be a secret.
  130. * Calling this method will not send a referrer header in the request, just as
  131. * if the user had opened a blank window and typed the url in themselves.
  132. */
  133. goog.editor.plugins.LinkBubble.prototype.stopReferrerLeaks = function() {
  134. // TODO(user): Right now only 2 plugins have this API to stop
  135. // referrer leaks. If more plugins need to do this, come up with a way to
  136. // enable the functionality in all plugins at once.
  137. this.stopReferrerLeaks_ = true;
  138. };
  139. /** @override */
  140. goog.editor.plugins.LinkBubble.prototype.getTrogClassId = function() {
  141. return 'LinkBubble';
  142. };
  143. /** @override */
  144. goog.editor.plugins.LinkBubble.prototype.getBubbleTargetFromSelection =
  145. function(selectedElement) {
  146. var bubbleTarget = goog.dom.getAncestorByTagNameAndClass(selectedElement,
  147. goog.dom.TagName.A);
  148. if (!bubbleTarget) {
  149. // See if the selection is touching the right side of a link, and if so,
  150. // show a bubble for that link. The check for "touching" is very brittle,
  151. // and currently only guarantees that it will pop up a bubble at the
  152. // position the cursor is placed at after the link dialog is closed.
  153. // NOTE(robbyw): This assumes this method is always called with
  154. // selected element = range.getContainerElement(). Right now this is true,
  155. // but attempts to re-use this method for other purposes could cause issues.
  156. // TODO(robbyw): Refactor this method to also take a range, and use that.
  157. var range = this.fieldObject.getRange();
  158. if (range && range.isCollapsed() && range.getStartOffset() == 0) {
  159. var startNode = range.getStartNode();
  160. var previous = startNode.previousSibling;
  161. if (previous && previous.tagName == goog.dom.TagName.A) {
  162. bubbleTarget = previous;
  163. }
  164. }
  165. }
  166. return /** @type {Element} */ (bubbleTarget);
  167. };
  168. /**
  169. * Set the optional function for getting the "test" link of a url.
  170. * @param {function(string) : string} func The function to use.
  171. */
  172. goog.editor.plugins.LinkBubble.prototype.setTestLinkUrlFn = function(func) {
  173. this.testLinkUrlFn_ = func;
  174. };
  175. /**
  176. * Returns the target element url for the bubble.
  177. * @return {string} The url href.
  178. * @protected
  179. */
  180. goog.editor.plugins.LinkBubble.prototype.getTargetUrl = function() {
  181. // Get the href-attribute through getAttribute() rather than the href property
  182. // because Google-Toolbar on Firefox with "Send with Gmail" turned on
  183. // modifies the href-property of 'mailto:' links but leaves the attribute
  184. // untouched.
  185. return this.getTargetElement().getAttribute('href') || '';
  186. };
  187. /** @override */
  188. goog.editor.plugins.LinkBubble.prototype.getBubbleType = function() {
  189. return goog.dom.TagName.A;
  190. };
  191. /** @override */
  192. goog.editor.plugins.LinkBubble.prototype.getBubbleTitle = function() {
  193. return goog.ui.editor.messages.MSG_LINK_CAPTION;
  194. };
  195. /** @override */
  196. goog.editor.plugins.LinkBubble.prototype.createBubbleContents = function(
  197. bubbleContainer) {
  198. var linkObj = this.getLinkToTextObj_();
  199. // Create linkTextSpan, show plain text for e-mail address or truncate the
  200. // text to <= 48 characters so that property bubbles don't grow too wide and
  201. // create a link if URL. Only linkify valid links.
  202. // TODO(robbyw): Repalce this color with a CSS class.
  203. var color = linkObj.valid ? 'black' : 'red';
  204. var linkTextSpan;
  205. if (goog.editor.Link.isLikelyEmailAddress(linkObj.linkText) ||
  206. !linkObj.valid) {
  207. linkTextSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
  208. {
  209. id: goog.editor.plugins.LinkBubble.LINK_TEXT_ID_,
  210. style: 'color:' + color
  211. }, this.dom_.createTextNode(linkObj.linkText));
  212. } else {
  213. var testMsgSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
  214. {id: goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_},
  215. MSG_LINK_BUBBLE_TEST_LINK);
  216. linkTextSpan = this.dom_.createDom(goog.dom.TagName.SPAN,
  217. {
  218. id: goog.editor.plugins.LinkBubble.LINK_TEXT_ID_,
  219. style: 'color:' + color
  220. }, '');
  221. var linkText = goog.string.truncateMiddle(linkObj.linkText, 48);
  222. // Actually creates a pseudo-link that can't be right-clicked to open in a
  223. // new tab, because that would avoid the logic to stop referrer leaks.
  224. this.createLink(goog.editor.plugins.LinkBubble.TEST_LINK_ID_,
  225. this.dom_.createTextNode(linkText).data,
  226. this.testLink,
  227. linkTextSpan);
  228. }
  229. var changeLinkSpan = this.createLinkOption(
  230. goog.editor.plugins.LinkBubble.CHANGE_LINK_SPAN_ID_);
  231. this.createLink(goog.editor.plugins.LinkBubble.CHANGE_LINK_ID_,
  232. MSG_LINK_BUBBLE_CHANGE, this.showLinkDialog_, changeLinkSpan);
  233. // This function is called multiple times - we have to reset the array.
  234. this.actionSpans_ = [];
  235. for (var i = 0; i < this.extraActions_.length; i++) {
  236. var action = this.extraActions_[i];
  237. var actionSpan = this.createLinkOption(action.spanId_);
  238. this.actionSpans_.push(actionSpan);
  239. this.createLink(action.linkId_, action.message_,
  240. function() {
  241. action.actionFn_(this.getTargetUrl());
  242. },
  243. actionSpan);
  244. }
  245. var removeLinkSpan = this.createLinkOption(
  246. goog.editor.plugins.LinkBubble.DELETE_LINK_SPAN_ID_);
  247. this.createLink(goog.editor.plugins.LinkBubble.DELETE_LINK_ID_,
  248. MSG_LINK_BUBBLE_REMOVE, this.deleteLink_, removeLinkSpan);
  249. this.onShow();
  250. var bubbleContents = this.dom_.createDom(goog.dom.TagName.DIV,
  251. {id: goog.editor.plugins.LinkBubble.LINK_DIV_ID_},
  252. testMsgSpan || '', linkTextSpan, changeLinkSpan);
  253. for (i = 0; i < this.actionSpans_.length; i++) {
  254. bubbleContents.appendChild(this.actionSpans_[i]);
  255. }
  256. bubbleContents.appendChild(removeLinkSpan);
  257. goog.dom.appendChild(bubbleContainer, bubbleContents);
  258. };
  259. /**
  260. * Tests the link by opening it in a new tab/window. Should be used as the
  261. * click event handler for the test pseudo-link.
  262. * @protected
  263. */
  264. goog.editor.plugins.LinkBubble.prototype.testLink = function() {
  265. goog.window.open(this.getTestLinkAction_(),
  266. {
  267. 'target': '_blank',
  268. 'noreferrer': this.stopReferrerLeaks_
  269. }, this.fieldObject.getAppWindow());
  270. };
  271. /**
  272. * Returns whether the URL should be considered invalid. This always returns
  273. * false in the base class, and should be overridden by subclasses that wish
  274. * to impose validity rules on URLs.
  275. * @param {string} url The url to check.
  276. * @return {boolean} Whether the URL should be considered invalid.
  277. */
  278. goog.editor.plugins.LinkBubble.prototype.isInvalidUrl = goog.functions.FALSE;
  279. /**
  280. * Gets the text to display for a link, based on the type of link
  281. * @return {Object} Returns an object of the form:
  282. * {linkText: displayTextForLinkTarget, valid: ifTheLinkIsValid}.
  283. * @private
  284. */
  285. goog.editor.plugins.LinkBubble.prototype.getLinkToTextObj_ = function() {
  286. var isError;
  287. var targetUrl = this.getTargetUrl();
  288. if (this.isInvalidUrl(targetUrl)) {
  289. /**
  290. * @desc Message shown in a link bubble when the link is not a valid url.
  291. */
  292. var MSG_INVALID_URL_LINK_BUBBLE = goog.getMsg('invalid url');
  293. targetUrl = MSG_INVALID_URL_LINK_BUBBLE;
  294. isError = true;
  295. } else if (goog.editor.Link.isMailto(targetUrl)) {
  296. targetUrl = targetUrl.substring(7); // 7 == "mailto:".length
  297. }
  298. return {linkText: targetUrl, valid: !isError};
  299. };
  300. /**
  301. * Shows the link dialog
  302. * @private
  303. */
  304. goog.editor.plugins.LinkBubble.prototype.showLinkDialog_ = function() {
  305. this.fieldObject.execCommand(goog.editor.Command.MODAL_LINK_EDITOR,
  306. new goog.editor.Link(
  307. /** @type {HTMLAnchorElement} */ (this.getTargetElement()),
  308. false));
  309. this.closeBubble();
  310. };
  311. /**
  312. * Deletes the link associated with the bubble
  313. * @private
  314. */
  315. goog.editor.plugins.LinkBubble.prototype.deleteLink_ = function() {
  316. this.fieldObject.dispatchBeforeChange();
  317. var link = this.getTargetElement();
  318. var child = link.lastChild;
  319. goog.dom.flattenElement(link);
  320. goog.editor.range.placeCursorNextTo(child, false);
  321. this.closeBubble();
  322. this.fieldObject.dispatchChange();
  323. };
  324. /**
  325. * Sets the proper state for the action links.
  326. * @protected
  327. * @override
  328. */
  329. goog.editor.plugins.LinkBubble.prototype.onShow = function() {
  330. var linkDiv = this.dom_.getElement(
  331. goog.editor.plugins.LinkBubble.LINK_DIV_ID_);
  332. if (linkDiv) {
  333. var testLinkSpan = this.dom_.getElement(
  334. goog.editor.plugins.LinkBubble.TEST_LINK_SPAN_ID_);
  335. if (testLinkSpan) {
  336. var url = this.getTargetUrl();
  337. goog.style.showElement(testLinkSpan, !goog.editor.Link.isMailto(url));
  338. }
  339. for (var i = 0; i < this.extraActions_.length; i++) {
  340. var action = this.extraActions_[i];
  341. var actionSpan = this.dom_.getElement(action.spanId_);
  342. if (actionSpan) {
  343. goog.style.showElement(actionSpan, action.toShowFn_(
  344. this.getTargetUrl()));
  345. }
  346. }
  347. }
  348. };
  349. /**
  350. * Gets the url for the bubble test link. The test link is the link in the
  351. * bubble the user can click on to make sure the link they entered is correct.
  352. * @return {string} The url for the bubble link href.
  353. * @private
  354. */
  355. goog.editor.plugins.LinkBubble.prototype.getTestLinkAction_ = function() {
  356. var targetUrl = this.getTargetUrl();
  357. return this.testLinkUrlFn_ ? this.testLinkUrlFn_(targetUrl) : targetUrl;
  358. };
  359. /**
  360. * Constructor for extra actions that can be added to the link bubble.
  361. * @param {string} spanId The ID for the span showing the action.
  362. * @param {string} linkId The ID for the link showing the action.
  363. * @param {string} message The text for the link showing the action.
  364. * @param {function(string):boolean} toShowFn Test function to determine whether
  365. * to show the action for the given URL.
  366. * @param {function(string):void} actionFn Action function to run when the
  367. * action is clicked. Takes the current target URL as a parameter.
  368. * @constructor
  369. */
  370. goog.editor.plugins.LinkBubble.Action = function(spanId, linkId, message,
  371. toShowFn, actionFn) {
  372. this.spanId_ = spanId;
  373. this.linkId_ = linkId;
  374. this.message_ = message;
  375. this.toShowFn_ = toShowFn;
  376. this.actionFn_ = actionFn;
  377. };