Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/extensions/VisualEditor/lib/ve/src/ve.utils.js
Ðазад
/*! * VisualEditor utilities. * * @copyright See AUTHORS.txt */ /** * Checks if an object is an instance of one or more classes. * * @param {Object} subject Object to check * @param {Function[]} classes Classes to compare with * @return {boolean} Object inherits from one or more of the classes */ ve.isInstanceOfAny = function ( subject, classes ) { let i = classes.length; while ( classes[ --i ] ) { if ( subject instanceof classes[ i ] ) { return true; } } return false; }; /** * Get a deeply nested property of an object using variadic arguments, protecting against * undefined property errors. * * `quux = OO.getProp( obj, 'foo', 'bar', 'baz' );` is equivalent to `quux = obj.foo.bar.baz;` * except that the former protects against JS errors if one of the intermediate properties * is undefined. Instead of throwing an error, this function will return undefined in * that case. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} obj * @param {...any} [keys] * @return {Object|undefined} obj[arguments[1]][arguments[2]].... or undefined */ ve.getProp = OO.getProp; /** * Set a deeply nested property of an object using variadic arguments, protecting against * undefined property errors. * * `OO.setProp( obj, 'foo', 'bar', 'baz' );` is equivalent to `obj.foo.bar = baz;` except that * the former protects against JS errors if one of the intermediate properties is * undefined. Instead of throwing an error, undefined intermediate properties will be * initialized to an empty object. If an intermediate property is not an object, or if obj itself * is not an object, this function will silently abort. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} obj * @param {...any} [keys] * @param {any} [value] */ ve.setProp = OO.setProp; /** * Delete a deeply nested property of an object using variadic arguments, protecting against * undefined property errors, and deleting resulting empty objects. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} obj * @param {...any} [keys] */ ve.deleteProp = OO.deleteProp; /** * Create a new object that is an instance of the same * constructor as the input, inherits from the same object * and contains the same own properties. * * This makes a shallow non-recursive copy of own properties. * To create a recursive copy of plain objects, use #copy. * * var foo = new Person( mom, dad ); * foo.setAge( 21 ); * var foo2 = OO.cloneObject( foo ); * foo.setAge( 22 ); * * // Then * foo2 !== foo; // true * foo2 instanceof Person; // true * foo2.getAge(); // 21 * foo.getAge(); // 22 * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} origin * @return {Object} Clone of origin */ ve.cloneObject = OO.cloneObject; /** * Get an array of all property values in an object. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} obj Object to get values from * @return {Array} List of object values */ ve.getObjectValues = OO.getObjectValues; /** * Use binary search to locate an element in a sorted array. * * searchFunc is given an element from the array. `searchFunc(elem)` must return a number * above 0 if the element we're searching for is to the right of (has a higher index than) elem, * below 0 if it is to the left of elem, or zero if it's equal to elem. * * To search for a specific value with a comparator function (a `function cmp(a,b)` that returns * above 0 if `a > b`, below 0 if `a < b`, and 0 if `a == b`), you can use * `searchFunc = cmp.bind( null, value )`. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Array} arr Array to search in * @param {Function} searchFunc Search function * @param {boolean} [forInsertion] If not found, return index where val could be inserted * @return {number|null} Index where val was found, or null if not found */ ve.binarySearch = OO.binarySearch; /** * Recursively compare properties between two objects. * * A false result may be caused by property inequality or by properties in one object missing from * the other. An asymmetrical test may also be performed, which checks only that properties in the * first object are present in the second object, but not the inverse. * * If either a or b is null or undefined it will be treated as an empty object. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object|undefined|null} a First object to compare * @param {Object|undefined|null} b Second object to compare * @param {boolean} [asymmetrical] Whether to check only that a's values are equal to b's * (i.e. a is a subset of b) * @return {boolean} If the objects contain the same values as each other */ ve.compare = OO.compare; /** * Create a plain deep copy of any kind of object. * * Copies are deep, and will either be an object or an array depending on `source`. * * See https://doc.wikimedia.org/oojs/master/OO.html * * @method * @param {Object} source Object to copy * @param {Function} [leafCallback] Applied to leaf values after they are cloned but before they are * added to the clone * @param {Function} [nodeCallback] Applied to all values before they are cloned. If the * nodeCallback returns a value other than undefined, the returned value is used instead of * attempting to clone. * @return {Object} Copy of source object */ ve.copy = OO.copy; /** * @method * @see OO.ui.debounce */ ve.debounce = OO.ui.debounce; /** * @method * @see OO.ui.throttle */ ve.throttle = OO.ui.throttle; /** * Create a jQuery.Deferred-compatible object * * See http://api.jquery.com/jQuery.Deferred/ * * @method * @return {jQuery.Deferred} */ ve.createDeferred = $.Deferred; /** * Create a promise which resolves when the list of promises has resolved * * @param {jQuery.Promise[]} promises List of promises * @return {jQuery.Promise} Promise which resolves when the list of promises has resolved */ ve.promiseAll = function ( promises ) { return $.when.apply( $, promises ); }; /** * Copy an array of DOM elements, optionally into a different document. * * @param {HTMLElement[]} domElements DOM elements to copy * @param {HTMLDocument} [doc] Document to create the copies in; if unset, simply clone each element * @return {HTMLElement[]} Copy of domElements with copies of each element */ ve.copyDomElements = function ( domElements, doc ) { return domElements.map( ( domElement ) => doc ? doc.importNode( domElement, true ) : domElement.cloneNode( true ) ); }; /** * Check if two arrays of DOM elements are equal (according to .isEqualNode()) * * @param {HTMLElement[]} domElements1 First array of DOM elements * @param {HTMLElement[]} domElements2 Second array of DOM elements * @return {boolean} All elements are pairwise equal */ ve.isEqualDomElements = function ( domElements1, domElements2 ) { let i = 0; const len = domElements1.length; if ( len !== domElements2.length ) { return false; } for ( ; i < len; i++ ) { if ( !domElements1[ i ].isEqualNode( domElements2[ i ] ) ) { return false; } } return true; }; /** * Compare two class lists, either whitespace separated strings or arrays * * Class lists are equivalent if they contain the same members, * excluding duplicates and ignoring order. * * @param {string[]|string} classList1 First class list * @param {string[]|string} classList2 Second class list * @return {boolean} Class lists are equivalent */ ve.compareClassLists = function ( classList1, classList2 ) { const removeEmpty = function ( c ) { return c !== ''; }; classList1 = Array.isArray( classList1 ) ? classList1 : classList1.trim().split( /\s+/ ); classList2 = Array.isArray( classList2 ) ? classList2 : classList2.trim().split( /\s+/ ); classList1 = classList1.filter( removeEmpty ); classList2 = classList2.filter( removeEmpty ); return ve.compare( OO.unique( classList1 ).sort(), OO.unique( classList2 ).sort() ); }; /** * Check to see if an object is a plain object (created using "{}" or "new Object"). * * See http://api.jquery.com/jQuery.isPlainObject/ * * @method * @param {Object} obj The object that will be checked to see if it's a plain object * @return {boolean} */ ve.isPlainObject = $.isPlainObject; /** * Check to see if an object is empty (contains no properties). * * See http://api.jquery.com/jQuery.isEmptyObject/ * * @method * @param {Object} obj The object that will be checked to see if it's empty * @return {boolean} */ ve.isEmptyObject = $.isEmptyObject; /** * Merge properties of one or more objects into another. * Preserves original object's inheritance (e.g. Array, Object, whatever). * In case of array or array-like objects only the indexed properties * are copied over. * Beware: If called with only one argument, it will consider * 'target' as 'source' and 'this' as 'target'. Which means * ve.extendObject( { a: 1 } ); sets ve.a = 1; * * See http://api.jquery.com/jQuery.extend/ * * @method * @param {boolean} [recursive=false] * @param {any} [target] Object that will receive the new properties * @param {...any} [sources] Variadic list of objects containing properties * to be merged into the target. * @return {any} Modified version of first or second argument */ ve.extendObject = $.extend; /** * Splice one array into another. * * This is the equivalent of arr.splice( offset, remove, d1, d2, d3, … ) except that arguments are * specified as an array rather than separate parameters. * * This method has been proven to be faster than using slice and concat to create a new array, but * performance tests should be conducted on each use of this method to verify this is true for the * particular use. Also, browsers change fast, never assume anything, always test everything. * * Includes a replacement for broken implementations of Array.prototype.splice(). * * @param {Array|ve.dm.BranchNode} arr Target object (must have `splice` method, object will be modified) * @param {number} offset Offset in arr to splice at. This MUST NOT be negative, unlike the * 'index' parameter in Array#splice. * @param {number} remove Number of elements to remove at the offset. May be zero * @param {Array} data Array of items to insert at the offset. Must be non-empty if remove=0 * @return {Array} Array of items removed */ ve.batchSplice = function ( arr, offset, remove, data ) { // We need to splice insertion in in batches, because of parameter list length limits which vary // cross-browser - 1024 seems to be a safe batch size on all browsers let index = 0, toRemove = remove, removed = []; const batchSize = 1024; if ( data.length === 0 ) { // Special case: data is empty, so we're just doing a removal // The code below won't handle that properly, so we do it here return arr.splice( offset, remove ); } while ( index < data.length ) { // Call arr.splice( offset, remove, i0, i1, i2, …, i1023 ); // Only set remove on the first call, and set it to zero on subsequent calls const spliced = arr.splice( index + offset, toRemove, ...data.slice( index, index + batchSize ) ); if ( toRemove > 0 ) { removed = spliced; } index += batchSize; toRemove = 0; } return removed; }; /** * Splice one array into another, replicating any holes * * Similar to arr.splice( offset, remove, ...data ), except holes in * data remain holes in arr. Optimized for length changes that are negative, zero, or * fairly small positive. * * @param {Array} arr Array to modify * @param {number} offset Offset in arr to splice at. This MUST NOT be negative, unlike the * 'index' parameter in Array#splice. * @param {number} remove Number of elements to remove at the offset. May be zero * @param {Array} data Array of items to insert at the offset * @return {Array} Array of items removed, with holes preserved */ ve.sparseSplice = function ( arr, offset, remove, data ) { const removed = [], endOffset = offset + remove, diff = data.length - remove; if ( data === arr ) { // Pathological case: arr and data are reference-identical data = data.slice(); } // Remove content without adjusting length arr.slice( offset, endOffset ).forEach( ( item, j ) => { removed[ j ] = item; delete arr[ offset + j ]; } ); // Adjust length if ( diff > 0 ) { // Grow with undefined values, then delete. (This is optimised for diff // comparatively small: otherwise, it would sometimes be quicker to relocate // each element of arr that lies above offset). ve.batchSplice( arr, endOffset, 0, new Array( diff ) ); for ( let i = endOffset + diff - 1; i >= endOffset; i-- ) { delete arr[ i ]; } } else if ( diff < 0 ) { // Shrink arr.splice( offset, -diff ); } // Insert new content data.forEach( ( item, j ) => { arr[ offset + j ] = item; } ); // Set removed.length in case there are holes at the end removed.length = remove; return removed; }; /** * Insert one array into another. * * Shortcut for `ve.batchSplice( arr, offset, 0, src )`. * * @see ve.batchSplice * @param {Array|ve.dm.BranchNode} arr Target object (must have `splice` method) * @param {number} offset Offset in arr where items will be inserted * @param {Array} src Items to insert at offset */ ve.insertIntoArray = function ( arr, offset, src ) { ve.batchSplice( arr, offset, 0, src ); }; /** * Push one array into another. * * This is the equivalent of arr.push( d1, d2, d3, … ) except that arguments are * specified as an array rather than separate parameters. * * @param {Array|ve.dm.BranchNode} arr Object supporting .push() to insert at the end of the array. Will be modified * @param {Array} data Array of items to insert. * @return {number} length of the new array */ ve.batchPush = function ( arr, data ) { // We need to push insertion in batches, because of parameter list length limits which vary // cross-browser - 1024 seems to be a safe batch size on all browsers let index = 0; const batchSize = 1024; if ( batchSize >= data.length ) { // Avoid slicing for small lists return arr.push( ...data ); } let length; while ( index < data.length ) { // Call arr.push( i0, i1, i2, …, i1023 ); length = arr.push( ...data.slice( index, index + batchSize ) ); index += batchSize; } return length; }; /** * Log data to the console. * * This implementation does nothing, to add a real implementation ve.debug needs to be loaded. * * @param {...any} [args] Data to log */ ve.log = ve.log || function () { // Don't do anything, this is just a stub }; /** * Log error to the console. * * This implementation does nothing, to add a real implementation ve.debug needs to be loaded. * * @param {...any} [args] Data to log */ ve.error = ve.error || function () { // Don't do anything, this is just a stub }; /** * Log an object to the console. * * This implementation does nothing, to add a real implementation ve.debug needs to be loaded. * * @param {Object} obj */ ve.dir = ve.dir || function () { // Don't do anything, this is just a stub }; /** * Deep freeze an object, making it immutable * * This implementation does nothing, to add a real implementation ve.freeze needs to be loaded. * * @param {Object} obj * @param {boolean} onlyProperties * @return {Object} */ ve.deepFreeze = ve.deepFreeze || function ( obj ) { // Don't do anything, this is just a stub return obj; }; /** * Get a localized message. * * @param {string} key Message key * @param {...any} [params] Message parameters * @return {string} Localized message */ ve.msg = function () { // Avoid using bind because ve.init.platform doesn't exist yet. // TODO: Fix dependency issues between ve.js and ve.init.platform return ve.init.platform.getMessage.apply( ve.init.platform, arguments ); }; /** * Get an HTML localized message with HTML or DOM arguments. * * @param {string} key Message key * @param {...any} [params] Message parameters * @return {Node[]} Localized message */ ve.htmlMsg = function () { // Avoid using bind because ve.init.platform doesn't exist yet. // TODO: Fix dependency issues between ve.js and ve.init.platform return ve.init.platform.getHtmlMessage.apply( ve.init.platform, arguments ); }; /** * Get platform config value(s) * * @param {string|string[]} key Config key, or list of keys * @return {any|Object} Config value, or keyed object of config values if list of keys provided */ ve.config = function () { return ve.init.platform.getConfig.apply( ve.init.platform, arguments ); }; /** * Get or set a user config value. * * @param {string|string[]|Object} key Config key, list of keys or object mapping keys to values * @param {any} [value] Value to set, if setting and key is a string * @return {any|Object|boolean} Config value, keyed object of config values if list of keys provided, * or success boolean if setting. */ ve.userConfig = function ( key ) { if ( arguments.length <= 1 && ( typeof key === 'string' || Array.isArray( key ) ) ) { // get( string key ) // get( Array keys ) return ve.init.platform.getUserConfig.apply( ve.init.platform, arguments ); } else { // set( Object values ) // set( key, value ) return ve.init.platform.setUserConfig.apply( ve.init.platform, arguments ); } }; /** * Convert a grapheme cluster offset to a byte offset. * * @param {string} text Text in which to calculate offset * @param {number} clusterOffset Grapheme cluster offset * @return {number} Byte offset */ ve.getByteOffset = function ( text, clusterOffset ) { return unicodeJS.graphemebreak.splitClusters( text ) .slice( 0, clusterOffset ) .join( '' ) .length; }; /** * Convert a byte offset to a grapheme cluster offset. * * @param {string} text Text in which to calculate offset * @param {number} byteOffset Byte offset * @return {number} Grapheme cluster offset */ ve.getClusterOffset = function ( text, byteOffset ) { return unicodeJS.graphemebreak.splitClusters( text.slice( 0, byteOffset ) ).length; }; /** * Get a text substring, taking care not to split grapheme clusters. * * @param {string} text Text to take the substring from * @param {number} start Start offset * @param {number} end End offset * @param {boolean} [outer=false] Include graphemes if the offset splits them * @return {string} Substring of text */ ve.graphemeSafeSubstring = function ( text, start, end, outer ) { // TODO: improve performance by incrementally inspecting characters around the offsets let unicodeStart = ve.getByteOffset( text, ve.getClusterOffset( text, start ) ), unicodeEnd = ve.getByteOffset( text, ve.getClusterOffset( text, end ) ); // If the selection collapses and we want an inner, then just return empty // otherwise we'll end up crossing over start and end if ( unicodeStart === unicodeEnd && !outer ) { return ''; } // The above calculations always move to the right of a multibyte grapheme. // Depending on the outer flag, we may want to move to the left: if ( unicodeStart > start && outer ) { unicodeStart = ve.getByteOffset( text, ve.getClusterOffset( text, start ) - 1 ); } if ( unicodeEnd > end && !outer ) { unicodeEnd = ve.getByteOffset( text, ve.getClusterOffset( text, end ) - 1 ); } return text.slice( unicodeStart, unicodeEnd ); }; /** * Escape non-word characters so they can be safely used as HTML attribute values. * * @param {string} value Attribute value to escape * @return {string} Escaped attribute value */ ve.escapeHtml = ( function () { function escape( value ) { switch ( value ) { case '\'': return '''; case '"': return '"'; case '<': return '<'; case '>': return '>'; case '&': return '&'; } } return function ( value ) { return value.replace( /['"<>&]/g, escape ); }; }() ); /** * Get the attributes of a DOM element as an object with key/value pairs. * * @param {HTMLElement} element * @return {Object} */ ve.getDomAttributes = function ( element ) { const result = {}; for ( let i = 0; i < element.attributes.length; i++ ) { result[ element.attributes[ i ].name ] = element.attributes[ i ].value; } return result; }; /** * Set the attributes of a DOM element as an object with key/value pairs. * * Use the `null` or `undefined` value to ensure an attribute's absence. * * @param {HTMLElement} element DOM element to apply attributes to * @param {Object} attributes Attributes to apply * @param {string[]} [allowedAttributes] List of attributes to exclusively allow (all lowercase names) */ ve.setDomAttributes = function ( element, attributes, allowedAttributes ) { // Duck-typing for attribute setting if ( !element.setAttribute || !element.removeAttribute ) { return; } for ( const key in attributes ) { if ( allowedAttributes && allowedAttributes.indexOf( key.toLowerCase() ) === -1 ) { continue; } if ( attributes[ key ] === undefined || attributes[ key ] === null ) { element.removeAttribute( key ); } else { element.setAttribute( key, attributes[ key ] ); } } }; /** * Get an HTML representation of a DOM element node, text node or comment node * * @param {Node} node The DOM node * @return {string} HTML representation of the node */ ve.getNodeHtml = function ( node ) { if ( node.nodeType === Node.ELEMENT_NODE ) { return node.outerHTML; } else { const div = document.createElement( 'div' ); div.appendChild( node.cloneNode( true ) ); return div.innerHTML; } }; /** * Build a summary of an HTML element. * * Summaries include node name, text, attributes and recursive summaries of children. * Used for serializing or comparing HTML elements. * * @private * @param {HTMLElement} element Element to summarize * @param {boolean} [includeHtml=false] Include an HTML summary for element nodes * @param {Function} [getAttributeSummary] Callback to modify the summary of an attribute * @param {string} [getAttributeSummary.name] Name of the attribute to modify. * @param {string} [getAttributeSummary.value] Value to return for the given attribute. * @return {Object} Summary of element. */ ve.getDomElementSummary = function ( element, includeHtml, getAttributeSummary ) { const summary = { type: element.nodeName.toLowerCase(), text: element.textContent, attributes: {}, children: [] }; if ( includeHtml && element.nodeType === Node.ELEMENT_NODE ) { summary.html = element.outerHTML; } // Gather attributes if ( element.attributes ) { for ( let i = 0; i < element.attributes.length; i++ ) { const name = element.attributes[ i ].name; if ( name === 'about' ) { // The about attribute is non-deterministic as we generate a new random // one whenever a node is cloned (see ve.dm.Node.static.cloneElement). // Exclude it from node comparisons. continue; } const value = element.attributes[ i ].value; summary.attributes[ name ] = getAttributeSummary ? getAttributeSummary( name, value ) : value; } } // Summarize children if ( element.childNodes ) { for ( let i = 0; i < element.childNodes.length; i++ ) { summary.children.push( ve.getDomElementSummary( element.childNodes[ i ], includeHtml ) ); } } return summary; }; /** * Callback for #copy to convert nodes to a comparable summary. * * @private * @param {Object} value Value in the object/array * @return {Object} DOM element summary if value is a node, otherwise just the value */ ve.convertDomElements = function ( value ) { // Use duck typing rather than instanceof Node; the latter doesn't always work correctly if ( value && value.nodeType ) { return ve.getDomElementSummary( value ); } return value; }; ve.visibleWhitespaceCharacters = { '\n': '\u21b5', // ↵ '\t': '\u279e' // ➞ }; /** * Check whether a given node is contentEditable * * Handles 'inherit', via checking isContentEditable. Knows to check for text * nodes, and will return whether the text node's parent is contentEditable. * * @param {HTMLElement|Text} node Node to check contenteditable status of * @return {boolean} Node is contenteditable */ ve.isContentEditable = function ( node ) { return ( node.nodeType === Node.TEXT_NODE ? node.parentNode : node ).isContentEditable; }; /** * Filter out metadata elements * * @param {Node[]} contents DOM nodes * @return {Node[]} Filtered DOM nodes */ ve.filterMetaElements = function ( contents ) { // Filter out link and style tags for T52043 // Previously filtered out meta tags, but restore these as they // can be made visible with CSS. // As of jQuery 3 we can't use $.not( 'tagName' ) as that doesn't // match text nodes. Also we can't $.remove these elements as they // aren't attached to anything. contents = contents.filter( ( node ) => node.tagName !== 'LINK' && node.tagName !== 'STYLE' ); // Also remove link and style tags nested inside other tags $( contents ).find( 'link, style' ).remove(); return contents; }; /** * Modify a set of DOM elements to resolve attributes in the context of another document. * * This performs node.setAttribute( 'attr', nodeInDoc[attr] ); for every node. * * Doesn't use jQuery to avoid document switching performance bug * * @param {HTMLElement|HTMLElement[]|NodeList|jQuery} elementsOrJQuery Set of DOM elements to modify. Passing a jQuery selector is deprecated. * @param {HTMLDocument} doc Document to resolve against (different from $elements' .ownerDocument) * @param {string[]} attrs Attributes to resolve */ ve.resolveAttributes = function ( elementsOrJQuery, doc, attrs ) { // Convert jQuery selections to plain arrays let elements = elementsOrJQuery.toArray ? elementsOrJQuery.toArray() : elementsOrJQuery; // Duck typing for array or NodeList :( if ( elements.length === undefined ) { elements = [ elements ]; } let attr; /** * Resolves the value of attr to the computed property value. * * @private * @param {HTMLElement} el Element */ function resolveAttribute( el ) { const nodeInDoc = doc.createElement( el.nodeName ); nodeInDoc.setAttribute( attr, el.getAttribute( attr ) ); if ( nodeInDoc[ attr ] ) { el.setAttribute( attr, nodeInDoc[ attr ] ); } } for ( let i = 0, iLen = elements.length; i < iLen; i++ ) { const element = elements[ i ]; for ( let j = 0, jLen = attrs.length; j < jLen; j++ ) { attr = attrs[ j ]; if ( element.hasAttribute( attr ) ) { resolveAttribute( element ); } Array.prototype.forEach.call( element.querySelectorAll( '[' + attr + ']' ), resolveAttribute ); } } }; /** * Make all links within a DOM element open in a new window * * @param {HTMLElement} container DOM element to search for links */ ve.targetLinksToNewWindow = function ( container ) { // Make all links open in a new window Array.prototype.forEach.call( container.querySelectorAll( 'a[href]' ), ( el ) => { ve.appendToRel( el, 'noopener' ); el.setAttribute( 'target', '_blank' ); } ); }; /** * Add a value to an element's rel attribute if it's not already present * * Rel is like class: it's actually a set, represented as a string. We don't * want to add the same value to the attribute if this function is called * repeatedly. This is mostly a placeholder for the relList property someday * becoming widely supported. * * @param {HTMLElement} element DOM element whose attribute should be checked * @param {string} value New rel value to be added */ ve.appendToRel = function ( element, value ) { const rel = element.getAttribute( 'rel' ); if ( !rel ) { // Avoid all that string-creation if it's not needed element.setAttribute( 'rel', value ); } else if ( ( ' ' + rel + ' ' ).indexOf( ' ' + value + ' ' ) === -1 ) { element.setAttribute( 'rel', rel + ' ' + value ); } }; /** * Check if a string is a valid URI component. * * A URI component is considered invalid if decodeURIComponent() throws an exception. * * @param {string} s String to test * @return {boolean} decodeURIComponent( s ) did not throw an exception * @see ve.safeDecodeURIComponent */ ve.isUriComponentValid = function ( s ) { try { decodeURIComponent( s ); } catch ( e ) { return false; } return true; }; /** * Safe version of decodeURIComponent() that doesn't throw exceptions. * * If the native decodeURIComponent() call threw an exception, the original string * will be returned. * * @param {string} s String to decode * @return {string} Decoded string, or same string if decoding failed * @see ve.isUriComponentValid */ ve.safeDecodeURIComponent = function ( s ) { try { s = decodeURIComponent( s ); } catch ( e ) {} return s; }; /** * Find the length of the common start sequence of one or more sequences * * Items are tested for sameness using === . * * @param {Array} sequences Array of sequences (arrays, strings etc) * @return {number} Common start sequence length (0 if sequences is empty) */ ve.getCommonStartSequenceLength = function ( sequences ) { if ( sequences.length === 0 ) { return 0; } let commonLength = 0; commonLengthLoop: while ( true ) { if ( commonLength >= sequences[ 0 ].length ) { break; } const val = sequences[ 0 ][ commonLength ]; for ( let i = 1, len = sequences.length; i < len; i++ ) { if ( sequences[ i ].length <= commonLength || sequences[ i ][ commonLength ] !== val ) { break commonLengthLoop; } } commonLength++; } return commonLength; }; /** * Find the nearest common ancestor of DOM nodes * * @param {...Node|null} nodes DOM nodes * @return {Node|null} Nearest common ancestor; or null if there is none / an argument is null */ ve.getCommonAncestor = function ( ...nodes ) { const nodeCount = nodes.length; if ( nodeCount === 0 ) { return null; } let minHeight = null; const chains = []; // Build every chain for ( let i = 0; i < nodeCount; i++ ) { const chain = []; let node = nodes[ i ]; while ( node !== null ) { chain.unshift( node ); node = node.parentNode; } if ( chain.length === 0 ) { // nodes[ i ] was null (so no common ancestor) return null; } if ( i > 0 && chain[ 0 ] !== chains[ chains.length - 1 ][ 0 ] ) { // No common ancestor (different documents or unattached branches) return null; } if ( minHeight === null || minHeight > chain.length ) { minHeight = chain.length; } chains.push( chain ); } // Step through chains in parallel, until they differ. // All chains are guaranteed to start with the common document element (or the common root // of an unattached branch) for ( let i = 1; i < minHeight; i++ ) { const node = chains[ 0 ][ i ]; for ( let j = 1; j < nodeCount; j++ ) { if ( node !== chains[ j ][ i ] ) { return chains[ 0 ][ i - 1 ]; } } } return chains[ 0 ][ minHeight - 1 ]; }; /** * Get the index of a node in its parentNode's childNode list * * @param {Node} node The node * @return {number} Index in parentNode's childNode list */ ve.parentIndex = function ( node ) { return Array.prototype.indexOf.call( node.parentNode.childNodes, node ); }; /** * Get the offset path from ancestor to offset in descendant * * @param {Node} ancestor The ancestor node * @param {Node} node The descendant node * @param {number} nodeOffset The offset in the descendant node * @return {number[]} The offset path */ ve.getOffsetPath = function ( ancestor, node, nodeOffset ) { const path = [ nodeOffset ]; while ( node !== ancestor ) { if ( node.parentNode === null ) { ve.log( node, 'is not a descendant of', ancestor ); throw new Error( 'Not a descendant' ); } path.unshift( ve.parentIndex( node ) ); node = node.parentNode; } return path; }; /** * Compare two tuples in lexicographical order. * * This function first compares `a[0]` with `b[0]`, then `a[1]` with `b[1]`, etc. * until it encounters a pair where `a[k] != b[k]`; then returns `a[k] - b[k]`. * * If `a[k] == b[k]` for every `k`, this function returns 0. * * If a and b are of unequal length, but `a[k] == b[k]` for all `k` that exist in both a and b, then * this function returns `Infinity` (if a is longer) or `-Infinity` (if b is longer). * * @param {number[]} a First tuple * @param {number[]} b Second tuple * @return {number} `a[k] - b[k]` where k is the lowest k such that `a[k] != b[k]` */ ve.compareTuples = function ( a, b ) { for ( let i = 0, len = Math.min( a.length, b.length ); i < len; i++ ) { if ( a[ i ] !== b[ i ] ) { return a[ i ] - b[ i ]; } } if ( a.length > b.length ) { return Infinity; } if ( a.length < b.length ) { return -Infinity; } return 0; }; /** * Compare two nodes for position in document * * Return null if either position is either null or incomparable (e.g. where one of the nodes * is detached or the nodes are from different documents). * * @param {Node|null} node1 First node * @param {number|null} offset1 First offset * @param {Node|null} node2 Second node * @param {number|null} offset2 Second offset * @return {number|null} negative, zero or positive number, or null if nodes null or incomparable */ ve.compareDocumentOrder = function ( node1, offset1, node2, offset2 ) { const commonAncestor = ve.getCommonAncestor( node1, node2 ); if ( commonAncestor === null ) { // Signal no common ancestor. In theory we could disallow this case, and check // the nodes for detachedness and same-documentness before each call, but such // guard checks would duplicate (either explicitly or implicitly) much of the // branch traversal performed in this method. return null; } return ve.compareTuples( ve.getOffsetPath( commonAncestor, node1, offset1 ), ve.getOffsetPath( commonAncestor, node2, offset2 ) ); }; /** * @typedef {Object} DomPosition * @memberof ve * @property {Node|null} node The node, or null if we stepped past the root node * @property {number|null} offset The offset, or null if we stepped past the root node * @property {ve.PositionStep[]} steps Steps taken */ /** * Get the closest matching DOM position in document order (forward or reverse) * * A DOM position is represented as an object with "node" and "offset" properties. * * The noDescend option can be used to exclude the positions inside certain element nodes; it is * a jQuery selector/function ( used as a test by $node.is() - see http://api.jquery.com/is/ ); * it defaults to ve.rejectsCursor . Void elements (those matching ve.isVoidElement) are always * excluded. * * As well as the end position, an array of ve.PositionSteps (node traversals) is returned. * The "stop" option is a boolean-valued function used to test each ve.PositionStep in turn. If * If it returns true, the position arrived at is returned; else the stepping continues to the * next matching DOM position. It defaults to ve.isHardCursorStep . * * Limitation: some DOM positions cannot actually hold the cursor; e.g. the start of the interior * of a table node. Browser cursoring jumps over text node/annotation node boundaries as if they * were invisible, and skips over most grapheme clusters e.g. 'x\u0301' (though not all e.g. * '\u062D\u0627'). Also, Chromium normalizes cursor focus/offset, when they are set, to the * start-most such cursor position (Firefox does not). * * @param {Object} position Start position * @param {Node} position.node Start node * @param {number} position.offset Start offset * @param {number} direction +1 for forward, -1 for reverse * @param {Object} options * @param {Function|string} [options.noDescend] Selector or function: nodes to skip over * @param {Function} [options.stop] Boolean-valued ve.PositionStep test function * @return {ve.DomPosition} The adjacent DOM position encountered * @see ve.isHardCursorStep */ ve.adjacentDomPosition = function ( position, direction, options ) { let node = position.node, offset = position.offset; const steps = []; const noDescend = options.noDescend || ve.rejectsCursor; const stop = options.stop || ve.isHardCursorStep; direction = direction < 0 ? -1 : 1; const forward = ( direction === 1 ); while ( true ) { // If we're at the node's leading edge, move to the adjacent position in the parent node if ( offset === ( forward ? node.length || node.childNodes.length : 0 ) ) { const step = new ve.PositionStep( node, 'leave' ); steps.push( step ); if ( node.parentNode === null ) { return { node: null, offset: null, steps: steps }; } offset = ve.parentIndex( node ) + ( forward ? 1 : 0 ); node = node.parentNode; if ( stop( step ) ) { return { node: node, offset: offset, steps: steps }; } // Else take another step continue; } // Else we're in the interior of a node // If we're in a text node, move to the position in this node at the next offset if ( node.nodeType === Node.TEXT_NODE ) { const step = new ve.PositionStep( node, 'internal', offset - ( forward ? 0 : 1 ) ); steps.push( step ); offset += direction; if ( stop( step ) ) { return { node: node, offset: offset, steps: steps }; } continue; } // Else we're in the interior of an element node const childNode = node.childNodes[ forward ? offset : offset - 1 ]; // Support: Firefox // If the child is uncursorable, or is an element matching noDescend, do not // descend into it: instead, return the position just beyond it in the current node if ( childNode.nodeType === Node.ELEMENT_NODE && ( ve.isVoidElement( childNode ) || $( childNode ).is( noDescend ) ) ) { const step = new ve.PositionStep( childNode, 'cross' ); steps.push( step ); offset += forward ? 1 : -1; if ( stop( step ) ) { return { node: node, offset: offset, steps: steps }; } // Else take another step continue; } // Go to the closest offset inside the child node node = childNode; offset = forward ? 0 : node.length || node.childNodes.length; const posStep = new ve.PositionStep( node, 'enter' ); steps.push( posStep ); if ( stop( posStep ) ) { return { node: node, offset: offset, steps: steps }; } } }; /** * Test whether a cursor movement step uses up a cursor press * * Essentially, this is true unless entering/exiting a contentEditable text/annotation node. * For instance in <#text>X</#text><b><#text>y</#text></b> * a single cursor press will jump from just after X to just after Y. * * @param {ve.PositionStep} step The cursor movement step to test * @return {boolean} Whether the cursor movement step uses up a cursor press * @see ve.adjacentDomPosition */ ve.isHardCursorStep = function ( step ) { if ( step.node.nodeType === Node.TEXT_NODE ) { return step.type === 'internal'; } return ve.isBlockElement( step.node ) || ve.rejectsCursor( step.node ); }; /** * Tests whether an adjacent cursor would be prevented from entering the node * * @param {Node} [node] Element node or text node; defaults to "this" if a Node * @return {boolean} Whether an adjacent cursor would be prevented from entering */ ve.rejectsCursor = function ( node ) { if ( !node && this instanceof Node ) { node = this; } if ( node.nodeType === node.TEXT_NODE ) { return false; } if ( ve.isVoidElement( node ) ) { return true; } // We don't need to check whether the ancestor-nearest contenteditable tag is // false, because if so then there can be no adjacent cursor. return node.contentEditable === 'false'; }; /** * @typedef {Object} ChangeOffsets * @memberof ve * @return {number} start Offset from start of first changed element * @return {number} end Offset from end of last changed element (nonoverlapping with start) */ /** * Count the common elements at the start and end of two sequences * * @param {Array|string} before The original sequence * @param {Array|string} after The modified sequence * @param {Function} [equals] Two-argument comparison returning boolean (defaults to ===) * @return {ve.ChangeOffsets|null} Change offsets (valid in both sequences), or null if unchanged */ ve.countEdgeMatches = function ( before, after, equals ) { if ( !equals ) { equals = function ( x, y ) { return x === y; }; } let start, end; const len = Math.min( before.length, after.length ); // Find maximal matching left slice for ( start = 0; start < len; start++ ) { if ( !equals( before[ start ], after[ start ] ) ) { break; } } if ( start === len && before.length === after.length ) { return null; } // Find maximal matching right slice that doesn't overlap the left slice for ( end = 0; end < len - start; end++ ) { if ( !equals( before[ before.length - 1 - end ], after[ after.length - 1 - end ] ) ) { break; } } return { start: start, end: end }; }; /** * Same as Object.entries, because we don't yet presume ES2017 * * @param {Object} ob The object * @return {Array[]} Entries, in the form [string, any] */ // eslint-disable-next-line es-x/no-object-entries ve.entries = Object.entries || ( ( ob ) => Object.keys( ob ).map( ( k ) => [ k, ob[ k ] ] ) );
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка