%PDF- %PDF-
Direktori : /home/vacivi36/ava/lib/editor/atto/yui/src/editor/js/ |
Current File : /home/vacivi36/ava/lib/editor/atto/yui/src/editor/js/selection.js |
// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * @module moodle-editor_atto-editor * @submodule selection */ /** * Selection functions for the Atto editor. * * See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details. * * @namespace M.editor_atto * @class EditorSelection */ function EditorSelection() {} EditorSelection.ATTRS = { }; EditorSelection.prototype = { /** * List of saved selections per editor instance. * * @property _selections * @private */ _selections: null, /** * A unique identifier for the last selection recorded. * * @property _lastSelection * @param lastselection * @type string * @private */ _lastSelection: null, /** * Whether focus came from a click event. * * This is used to determine whether to restore the selection or not. * * @property _focusFromClick * @type Boolean * @default false * @private */ _focusFromClick: false, /** * Whether if the last gesturemovestart event target was contained in this editor or not. * * @property _gesturestartededitor * @type Boolean * @default false * @private */ _gesturestartededitor: false, /** * Set up the watchers for selection save and restoration. * * @method setupSelectionWatchers * @chainable */ setupSelectionWatchers: function() { // Save the selection when a change was made. this.on('atto:selectionchanged', this.saveSelection, this); this.editor.on('focus', this.restoreSelection, this); // Do not restore selection when focus is from a click event. this.editor.on('mousedown', function() { this._focusFromClick = true; }, this); // Copy the current value back to the textarea when focus leaves us and save the current selection. this.editor.on('blur', function() { // Clear the _focusFromClick value. this._focusFromClick = false; // Update the original text area. this.updateOriginal(); }, this); this.editor.on(['keyup', 'focus'], function(e) { Y.soon(Y.bind(this._hasSelectionChanged, this, e)); }, this); Y.one(document.body).on('gesturemovestart', function(e) { if (this._wrapper.contains(e.target._node)) { this._gesturestartededitor = true; } else { this._gesturestartededitor = false; } }, null, this); Y.one(document.body).on('gesturemoveend', function(e) { if (!this._gesturestartededitor) { // Ignore the event if movestart target was not contained in the editor. return; } Y.soon(Y.bind(this._hasSelectionChanged, this, e)); }, { // Standalone will make sure all editors receive the end event. standAlone: true }, this); return this; }, /** * Work out if the cursor is in the editable area for this editor instance. * * @method isActive * @return {boolean} */ isActive: function() { var range = rangy.createRange(), selection = rangy.getSelection(); if (!selection.rangeCount) { // If there was no range count, then there is no selection. return false; } // We can't be active if the editor doesn't have focus at the moment. if (!document.activeElement || !(this.editor.compareTo(document.activeElement) || this.editor.contains(document.activeElement))) { return false; } // Check whether the range intersects the editor selection. range.selectNode(this.editor.getDOMNode()); return range.intersectsRange(selection.getRangeAt(0)); }, /** * Create a cross browser selection object that represents a YUI node. * * @method getSelectionFromNode * @param {Node} YUI Node to base the selection upon. * @return {[rangy.Range]} */ getSelectionFromNode: function(node) { var range = rangy.createRange(); range.selectNode(node.getDOMNode()); return [range]; }, /** * Save the current selection to an internal property. * * This allows more reliable return focus, helping improve keyboard navigation. * * Should be used in combination with {{#crossLink "M.editor_atto.EditorSelection/restoreSelection"}}{{/crossLink}}. * * @method saveSelection */ saveSelection: function() { if (this.isActive()) { this._selections = this.getSelection(); } }, /** * Restore any stored selection when the editor gets focus again. * * Should be used in combination with {{#crossLink "M.editor_atto.EditorSelection/saveSelection"}}{{/crossLink}}. * * @method restoreSelection */ restoreSelection: function() { if (!this._focusFromClick) { if (this._selections) { this.setSelection(this._selections); } } this._focusFromClick = false; }, /** * Get the selection object that can be passed back to setSelection. * * @method getSelection * @return {array} An array of rangy ranges. */ getSelection: function() { return rangy.getSelection().getAllRanges(); }, /** * Check that a YUI node it at least partly contained by the current selection. * * @method selectionContainsNode * @param {Node} The node to check. * @return {boolean} */ selectionContainsNode: function(node) { return rangy.getSelection().containsNode(node.getDOMNode(), true); }, /** * Runs a filter on each node in the selection, and report whether the * supplied selector(s) were found in the supplied Nodes. * * By default, all specified nodes must match the selection, but this * can be controlled with the requireall property. * * @method selectionFilterMatches * @param {String} selector * @param {NodeList} [selectednodes] For performance this should be passed. If not passed, this will be looked up each time. * @param {Boolean} [requireall=true] Used to specify that "any" match is good enough. * @return {Boolean} */ selectionFilterMatches: function(selector, selectednodes, requireall) { if (typeof requireall === 'undefined') { requireall = true; } if (!selectednodes) { // Find this because it was not passed as a param. selectednodes = this.getSelectedNodes(); } var allmatch = selectednodes.size() > 0, anymatch = false; var editor = this.editor, stopFn = function(node) { // The function getSelectedNodes only returns nodes within the editor, so this test is safe. return node === editor; }; // If we do not find at least one match in the editor, no point trying to find them in the selection. if (!editor.one(selector)) { return false; } selectednodes.each(function(node) { // Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing. if (requireall) { // Check for at least one failure. if (!allmatch || !node.ancestor(selector, true, stopFn)) { allmatch = false; } } else { // Check for at least one match. if (!anymatch && node.ancestor(selector, true, stopFn)) { anymatch = true; } } }, this); if (requireall) { return allmatch; } else { return anymatch; } }, /** * Get the deepest possible list of nodes in the current selection. * * @method getSelectedNodes * @return {NodeList} */ getSelectedNodes: function() { var results = new Y.NodeList(), nodes, selection, range, node, i; selection = rangy.getSelection(); if (selection.rangeCount) { range = selection.getRangeAt(0); } else { // Empty range. range = rangy.createRange(); } if (range.collapsed) { // We do not want to select all the nodes in the editor if we managed to // have a collapsed selection directly in the editor. // It's also possible for the commonAncestorContainer to be the document, which selectNode does not handle // so we must filter that out here too. if (range.commonAncestorContainer !== this.editor.getDOMNode() && range.commonAncestorContainer !== Y.config.doc) { range = range.cloneRange(); range.selectNode(range.commonAncestorContainer); } } nodes = range.getNodes(); for (i = 0; i < nodes.length; i++) { node = Y.one(nodes[i]); if (this.editor.contains(node)) { results.push(node); } } return results; }, /** * Check whether the current selection has changed since this method was last called. * * If the selection has changed, the atto:selectionchanged event is also fired. * * @method _hasSelectionChanged * @private * @param {EventFacade} e * @return {Boolean} */ _hasSelectionChanged: function(e) { var selection = rangy.getSelection(), range, changed = false; if (selection.rangeCount) { range = selection.getRangeAt(0); } else { // Empty range. range = rangy.createRange(); } if (this._lastSelection) { if (!this._lastSelection.equals(range)) { changed = true; return this._fireSelectionChanged(e); } } this._lastSelection = range; return changed; }, /** * Fires the atto:selectionchanged event. * * When the selectionchanged event is fired, the following arguments are provided: * - event : the original event that lead to this event being fired. * - selectednodes : an array containing nodes that are entirely selected of contain partially selected content. * * @method _fireSelectionChanged * @private * @param {EventFacade} e */ _fireSelectionChanged: function(e) { this.fire('atto:selectionchanged', { event: e, selectedNodes: this.getSelectedNodes() }); }, /** * Get the DOM node representing the common anscestor of the selection nodes. * * @method getSelectionParentNode * @return {Element|boolean} The DOM Node for this parent, or false if no seletion was made. */ getSelectionParentNode: function() { var selection = rangy.getSelection(); if (selection.rangeCount) { return selection.getRangeAt(0).commonAncestorContainer; } return false; }, /** * Set the current selection. Used to restore a selection. * * @method selection * @param {array} ranges A list of rangy.range objects in the selection. */ setSelection: function(ranges) { var selection = rangy.getSelection(); selection.setRanges(ranges); }, /** * Inserts the given HTML into the editable content at the currently focused point. * * @method insertContentAtFocusPoint * @param {String} html * @return {Node} The YUI Node object added to the DOM. */ insertContentAtFocusPoint: function(html) { var selection = rangy.getSelection(), range, node = Y.Node.create(html); if (selection.rangeCount) { range = selection.getRangeAt(0); } if (range) { range.deleteContents(); range.collapse(false); var currentnode = node.getDOMNode(), last = currentnode.lastChild || currentnode; range.insertNode(currentnode); range.collapseAfter(last); selection.setSingleRange(range); } return node; } }; Y.Base.mix(Y.M.editor_atto.Editor, [EditorSelection]);