[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-atto_link-button', function (Y, NAME) { 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /* 19 * @package atto_link 20 * @copyright 2013 Damyon Wiese <damyon@moodle.com> 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 /** 25 * @module moodle-atto_link-button 26 */ 27 28 /** 29 * Atto text editor link plugin. 30 * 31 * @namespace M.atto_link 32 * @class button 33 * @extends M.editor_atto.EditorPlugin 34 */ 35 36 var COMPONENTNAME = 'atto_link', 37 CSS = { 38 NEWWINDOW: 'atto_link_openinnewwindow', 39 URLINPUT: 'atto_link_urlentry' 40 }, 41 SELECTORS = { 42 URLINPUT: '.atto_link_urlentry' 43 }, 44 TEMPLATE = '' + 45 '<form class="atto_form">' + 46 '<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' + 47 '<input class="fullwidth url {{CSS.URLINPUT}}" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>' + 48 49 // Add the repository browser button. 50 '{{#if showFilepicker}}' + 51 '<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button>' + 52 '<br/>' + 53 '{{/if}}' + 54 '<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' + 55 '<label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label>' + 56 '<br/>' + 57 '<div class="mdl-align">' + 58 '<br/>' + 59 '<button type="submit" class="submit">{{get_string "createlink" component}}</button>' + 60 '</div>' + 61 '</form>'; 62 Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { 63 64 /** 65 * A reference to the current selection at the time that the dialogue 66 * was opened. 67 * 68 * @property _currentSelection 69 * @type Range 70 * @private 71 */ 72 _currentSelection: null, 73 74 /** 75 * A reference to the dialogue content. 76 * 77 * @property _content 78 * @type Node 79 * @private 80 */ 81 _content: null, 82 83 initializer: function() { 84 // Add the link button first. 85 this.addButton({ 86 icon: 'e/insert_edit_link', 87 keys: '75', 88 callback: this._displayDialogue, 89 tags: 'a', 90 tagMatchRequiresAll: false 91 }); 92 93 // And then the unlink button. 94 this.addButton({ 95 buttonName: 'unlink', 96 callback: this._unlink, 97 icon: 'e/remove_link', 98 title: 'unlink', 99 100 // Watch the following tags and add/remove highlighting as appropriate: 101 tags: 'a', 102 tagMatchRequiresAll: false 103 }); 104 }, 105 106 /** 107 * Display the link editor. 108 * 109 * @method _displayDialogue 110 * @private 111 */ 112 _displayDialogue: function() { 113 // Store the current selection. 114 this._currentSelection = this.get('host').getSelection(); 115 if (this._currentSelection === false) { 116 return; 117 } 118 119 var dialogue = this.getDialogue({ 120 headerContent: M.util.get_string('createlink', COMPONENTNAME), 121 focusAfterHide: true, 122 focusOnShowSelector: SELECTORS.URLINPUT 123 }); 124 125 // Set the dialogue content, and then show the dialogue. 126 dialogue.set('bodyContent', this._getDialogueContent()); 127 128 // Resolve anchors in the selected text. 129 this._resolveAnchors(); 130 dialogue.show(); 131 }, 132 133 /** 134 * If there is selected text and it is part of an anchor link, 135 * extract the url (and target) from the link (and set them in the form). 136 * 137 * @method _resolveAnchors 138 * @private 139 */ 140 _resolveAnchors: function() { 141 // Find the first anchor tag in the selection. 142 var selectednode = this.get('host').getSelectionParentNode(), 143 anchornodes, 144 anchornode, 145 url, 146 target; 147 148 // Note this is a document fragment and YUI doesn't like them. 149 if (!selectednode) { 150 return; 151 } 152 153 anchornodes = this._findSelectedAnchors(Y.one(selectednode)); 154 if (anchornodes.length > 0) { 155 anchornode = anchornodes[0]; 156 this._currentSelection = this.get('host').getSelectionFromNode(anchornode); 157 url = anchornode.getAttribute('href'); 158 target = anchornode.getAttribute('target'); 159 if (url !== '') { 160 this._content.one('.url').setAttribute('value', url); 161 } 162 if (target === '_blank') { 163 this._content.one('.newwindow').setAttribute('checked', 'checked'); 164 } else { 165 this._content.one('.newwindow').removeAttribute('checked'); 166 } 167 } 168 }, 169 170 /** 171 * Update the dialogue after a link was selected in the File Picker. 172 * 173 * @method _filepickerCallback 174 * @param {object} params The parameters provided by the filepicker 175 * containing information about the link. 176 * @private 177 */ 178 _filepickerCallback: function(params) { 179 this.getDialogue() 180 .set('focusAfterHide', null) 181 .hide(); 182 183 if (params.url !== '') { 184 // Add the link. 185 this._setLinkOnSelection(params.url); 186 187 // And mark the text area as updated. 188 this.markUpdated(); 189 } 190 }, 191 192 /** 193 * The link was inserted, so make changes to the editor source. 194 * 195 * @method _setLink 196 * @param {EventFacade} e 197 * @private 198 */ 199 _setLink: function(e) { 200 var input, 201 value; 202 203 e.preventDefault(); 204 this.getDialogue({ 205 focusAfterHide: null 206 }).hide(); 207 208 input = this._content.one('.url'); 209 210 value = input.get('value'); 211 if (value !== '') { 212 213 // We add a prefix if it is not already prefixed. 214 value = value.trim(); 215 var expr = new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/); 216 if (!expr.test(value)) { 217 value = 'http://' + value; 218 } 219 220 // Add the link. 221 this._setLinkOnSelection(value); 222 223 this.markUpdated(); 224 } 225 }, 226 227 /** 228 * Final step setting the anchor on the selection. 229 * 230 * @private 231 * @method _setLinkOnSelection 232 * @param {String} url URL the link will point to. 233 * @return {Node} The added Node. 234 */ 235 _setLinkOnSelection: function(url) { 236 var host = this.get('host'), 237 link, 238 selectednode, 239 target, 240 anchornodes; 241 242 this.editor.focus(); 243 host.setSelection(this._currentSelection); 244 245 if (this._currentSelection[0].collapsed) { 246 // Firefox cannot add links when the selection is empty so we will add it manually. 247 link = Y.Node.create('<a>' + url + '</a>'); 248 link.setAttribute('href', url); 249 250 // Add the node and select it to replicate the behaviour of execCommand. 251 selectednode = host.insertContentAtFocusPoint(link.get('outerHTML')); 252 host.setSelection(host.getSelectionFromNode(selectednode)); 253 } else { 254 document.execCommand('unlink', false, null); 255 document.execCommand('createLink', false, url); 256 257 // Now set the target. 258 selectednode = host.getSelectionParentNode(); 259 } 260 261 // Note this is a document fragment and YUI doesn't like them. 262 if (!selectednode) { 263 return; 264 } 265 266 anchornodes = this._findSelectedAnchors(Y.one(selectednode)); 267 // Add new window attributes if requested. 268 Y.Array.each(anchornodes, function(anchornode) { 269 target = this._content.one('.newwindow'); 270 if (target.get('checked')) { 271 anchornode.setAttribute('target', '_blank'); 272 } else { 273 anchornode.removeAttribute('target'); 274 } 275 }, this); 276 277 return selectednode; 278 }, 279 280 /** 281 * Look up and down for the nearest anchor tags that are least partly contained in the selection. 282 * 283 * @method _findSelectedAnchors 284 * @param {Node} node The node to search under for the selected anchor. 285 * @return {Node|Boolean} The Node, or false if not found. 286 * @private 287 */ 288 _findSelectedAnchors: function(node) { 289 var tagname = node.get('tagName'), 290 hit, hits; 291 292 // Direct hit. 293 if (tagname && tagname.toLowerCase() === 'a') { 294 return [node]; 295 } 296 297 // Search down but check that each node is part of the selection. 298 hits = []; 299 node.all('a').each(function(n) { 300 if (!hit && this.get('host').selectionContainsNode(n)) { 301 hits.push(n); 302 } 303 }, this); 304 if (hits.length > 0) { 305 return hits; 306 } 307 // Search up. 308 hit = node.ancestor('a'); 309 if (hit) { 310 return [hit]; 311 } 312 return []; 313 }, 314 315 /** 316 * Generates the content of the dialogue. 317 * 318 * @method _getDialogueContent 319 * @return {Node} Node containing the dialogue content 320 * @private 321 */ 322 _getDialogueContent: function() { 323 var canShowFilepicker = this.get('host').canShowFilepicker('link'), 324 template = Y.Handlebars.compile(TEMPLATE); 325 326 this._content = Y.Node.create(template({ 327 showFilepicker: canShowFilepicker, 328 component: COMPONENTNAME, 329 CSS: CSS 330 })); 331 332 this._content.one('.submit').on('click', this._setLink, this); 333 if (canShowFilepicker) { 334 this._content.one('.openlinkbrowser').on('click', function(e) { 335 e.preventDefault(); 336 this.get('host').showFilepicker('link', this._filepickerCallback, this); 337 }, this); 338 } 339 340 return this._content; 341 }, 342 343 /** 344 * Unlinks the current selection. 345 * If the selection is empty (e.g. the cursor is placed within a link), 346 * then the whole link is unlinked. 347 * 348 * @method _unlink 349 * @private 350 */ 351 _unlink: function() { 352 var host = this.get('host'), 353 range = host.getSelection(); 354 355 if (range && range.length) { 356 if (range[0].startOffset === range[0].endOffset) { 357 // The cursor was placed in the editor but there was no selection - select the whole parent. 358 var nodes = host.getSelectedNodes(); 359 if (nodes) { 360 // We need to unlink each anchor individually - we cannot select a range because it may only consist of a 361 // fragment of an anchor. Selecting the parent would be dangerous because it may contain other links which 362 // would then be unlinked too. 363 nodes.each(function(node) { 364 // We need to select the whole anchor node for this to work in some browsers. 365 // We only need to search up because getSeletedNodes returns all Nodes in the selection. 366 var anchor = node.ancestor('a', true); 367 if (anchor) { 368 // Set the selection to the whole of the first anchro. 369 host.setSelection(host.getSelectionFromNode(anchor)); 370 371 // Call the browser unlink. 372 document.execCommand('unlink', false, null); 373 } 374 }, this); 375 376 // And mark the text area as updated. 377 this.markUpdated(); 378 } 379 } else { 380 // Call the browser unlink. 381 document.execCommand('unlink', false, null); 382 383 // And mark the text area as updated. 384 this.markUpdated(); 385 } 386 } 387 } 388 }); 389 390 391 }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |