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