API Docs for: 3.11.0-git
Show:

File: src/sm-editor/js/extensions/editor-link.js

  1. /*jshint expr:true, onevar:false */
  2.  
  3. /**
  4. Provides the `Editor.Link` extension.
  5.  
  6. @module gallery-sm-editor
  7. @submodule gallery-sm-editor-link
  8. **/
  9.  
  10. /**
  11. Extension for `Editor.Base` that enables inserting links
  12.  
  13. Provides support for the following commands:
  14.  
  15. - createLink
  16. - unlink
  17.  
  18. @class Editor.Link
  19. @extends Base
  20. @extensionfor Editor.Base
  21. **/
  22.  
  23. (function() {
  24. var EditorLink = Y.Base.create('editorLink', Y.Base, [], {
  25. // -- Public Properties ----------------------------------------------------
  26.  
  27. /**
  28. Hash of link commands supported by this editor.
  29.  
  30. Names should correspond with valid `execCommand()` command names. Values
  31. are properties in the following format:
  32.  
  33. @property {Object} linkCommands
  34. @param {Function|String} commandFn
  35. @param {Function|String} [queryFn]
  36. **/
  37. linkCommands: {
  38. createLink: {
  39. commandFn: '_createLink',
  40. queryFn: 'isLink'
  41. },
  42.  
  43. unlink: {
  44. commandFn: '_unlink',
  45. queryFn: 'isLink'
  46. }
  47. },
  48.  
  49. /**
  50. HTML tags supported by this editor. Unsupported tags will be treated
  51. as text
  52.  
  53. @property {String} supportedTags
  54. **/
  55. linkTags: 'a',
  56.  
  57. /**
  58. HTML Template for building an anchor node
  59.  
  60. @property {Object} linkTemplate
  61. **/
  62. linkTemplate: '<a href="{href}" target="{target}"></a>',
  63.  
  64.  
  65. // -- Lifecycle ------------------------------------------------------------
  66.  
  67. initializer: function () {
  68. this.commands = Y.merge(this.commands, this.linkCommands);
  69.  
  70. if (this.supportedTags) {
  71. this.supportedTags += ',' + this.linkTags;
  72. } else {
  73. this.supportedTags = this.linkTags;
  74. }
  75. },
  76.  
  77.  
  78. // -- Public Methods -------------------------------------------------------
  79.  
  80. /**
  81. Returns whether or not the current range is entirely in an anchor element
  82.  
  83. @method isLink
  84. @return {boolean} `true` if the range is contained in an anchor element,
  85. `false` otherwise
  86. **/
  87. isLink: function () {
  88. return !!this._getAnchorNode();
  89. },
  90.  
  91.  
  92. // -- Protected Methods ----------------------------------------------------
  93.  
  94. /**
  95. Returns the nearest ancestor anchor that entirely contains
  96. the current range
  97.  
  98. @method _getAnchorNode
  99. @return {Node} The containing anchor element
  100. @protected
  101. **/
  102. _getAnchorNode: function() {
  103. this.focus();
  104.  
  105. var parentNode = this.selection.range().shrink().parentNode();
  106.  
  107. return parentNode.ancestor(this.linkTags, true);
  108. },
  109.  
  110.  
  111. /**
  112. Implementation for the `createLink` command
  113.  
  114. Wraps the currently selected range in an anchor `<a>` tag
  115.  
  116. @method _createLink
  117. @param {Object} options
  118. @param {String} options.href
  119. @param {String} [options.target=_self]
  120. @param {String} [options.text]
  121. @protected
  122. **/
  123. _createLink: function(options){
  124. var range = this.selection.range(),
  125. anchorNode, styleNodes;
  126.  
  127. if (!range) {
  128. return;
  129. }
  130.  
  131. if (this.isLink()) {
  132. this._unlink();
  133. range = this.selection.range();
  134. }
  135.  
  136. options || (options = {});
  137. options.href = encodeURI(options.href || '');
  138. options.target = encodeURIComponent(options.target || '_self');
  139.  
  140. anchorNode = Y.Node.create(Y.Lang.sub(this.linkTemplate, options));
  141. styleNodes = this._getStyleNodes(range);
  142.  
  143. anchorNode.append(styleNodes);
  144.  
  145. range.insertNode(anchorNode);
  146.  
  147. if (options.text && options.text !== range.toString()) {
  148. var firstChild = anchorNode.get('firstChild');
  149.  
  150. if (this._isStyleNode(firstChild)) {
  151. firstChild.set('text', options.text);
  152. anchorNode.setHTML(firstChild);
  153. } else {
  154. anchorNode.set('text', options.text);
  155. }
  156. }
  157.  
  158. range.selectNode(anchorNode).collapse();
  159.  
  160. this.selection.select(range);
  161. },
  162.  
  163.  
  164. /**
  165. Removes link by replacing the anchor element with the child nodes
  166. of the anchor
  167.  
  168. The anchor element will be removed from the DOM and destroyed.
  169.  
  170. @method _unlink
  171. @protected
  172. **/
  173. _unlink: function() {
  174. var selection = this.selection,
  175. anchorNode;
  176.  
  177. // we can use the native unlink command once we have bookmarking
  178. // in place, but firefox selects adjacent text nodes after unlink
  179.  
  180. if (anchorNode = this._getAnchorNode()) {
  181. var firstChild = anchorNode.get('firstChild'),
  182. lastChild = anchorNode.get('lastChild'),
  183. range = selection.range();
  184.  
  185. // only need to unwrap one of the children to unwrap the
  186. // whole anchorNode
  187. firstChild.unwrap();
  188.  
  189. anchorNode.destroy();
  190.  
  191. range.startNode(firstChild, 0);
  192. range.endNode(lastChild, 'after');
  193.  
  194. selection.select(range.shrink({trim: true}));
  195. }
  196. }
  197. });
  198.  
  199. Y.namespace('Editor').Link = EditorLink;
  200.  
  201. }());
  202.