Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/resources/src/mediawiki.util/util.js
Ðазад
'use strict'; let config = require( './config.json' ); const portletLinkOptions = require( './portletLinkOptions.json' ); const infinityValues = require( './infinityValues.json' ); require( './jquery.accessKeyLabel.js' ); /** * Encode the string like PHP's rawurlencode. * * @ignore * @param {string} str String to be encoded. * @return {string} Encoded string */ function rawurlencode( str ) { return encodeURIComponent( String( str ) ) .replace( /!/g, '%21' ) .replace( /'/g, '%27' ) .replace( /\(/g, '%28' ) .replace( /\)/g, '%29' ) .replace( /\*/g, '%2A' ) .replace( /~/g, '%7E' ); } /** * Private helper function used by util.escapeId*() * * @ignore * @param {string} str String to be encoded * @param {string} mode Encoding mode, see documentation at * MainConfigSchema::FragmentMode. * @return {string} Encoded string */ function escapeIdInternal( str, mode ) { str = String( str ); switch ( mode ) { case 'html5': return str.replace( / /g, '_' ); case 'legacy': return rawurlencode( str.replace( / /g, '_' ) ) .replace( /%3A/g, ':' ) .replace( /%/g, '.' ); default: throw new Error( 'Unrecognized ID escaping mode ' + mode ); } } /** * Library providing useful common skin-agnostic utility functions. Please see * [mediawiki.util]{@link module:mediawiki.util}. * * Alias for the [mediawiki.util]{@link module:mediawiki.util} module. * * @namespace mw.util */ /** * Utility library provided by the `mediawiki.util` ResourceLoader module. Accessible inside ResourceLoader modules * or for gadgets as part of the [mw global object]{@link mw}. * * @example * // Inside MediaWiki extensions * const util = require( 'mediawiki.util' ); * // In gadgets * const mwUtil = mw.util; * @exports mediawiki.util */ const util = { /** * Encode the string like PHP's rawurlencode. * * @method * @param {string} str String to be encoded. * @return {string} Encoded string */ rawurlencode: rawurlencode, /** * Encode a string as CSS id, for use as HTML id attribute value. * * Analog to `Sanitizer::escapeIdForAttribute()` in PHP. * * @since 1.30 * @param {string} str String to encode * @return {string} Encoded string */ escapeIdForAttribute: function ( str ) { return escapeIdInternal( str, config.FragmentMode[ 0 ] ); }, /** * Encode a string as URL fragment, for use as HTML anchor link. * * Analog to `Sanitizer::escapeIdForLink()` in PHP. * * @since 1.30 * @param {string} str String to encode * @return {string} Encoded string */ escapeIdForLink: function ( str ) { return escapeIdInternal( str, config.FragmentMode[ 0 ] ); }, /** * Get the target element from a link hash. * * This is the same element as you would get from * document.querySelectorAll(':target'), but can be used on * an arbitrary hash fragment, or after pushState/replaceState * has been used. * * Link fragments can be unencoded, fully encoded or partially * encoded, as defined in the spec. * * We can't just use decodeURI as that assumes the fragment * is fully encoded, and throws an error on a string like '%A', * so we use the percent-decode. * * @param {string} [hash] Hash fragment, without the leading '#'. * Taken from location.hash if omitted. * @return {HTMLElement|null} Element, if found */ getTargetFromFragment: function ( hash ) { hash = hash || location.hash.slice( 1 ); if ( !hash ) { // Firefox emits a console warning if you pass an empty string // to getElementById (T272844). return null; } // Per https://html.spec.whatwg.org/multipage/browsing-the-web.html#target-element // we try the raw fragment first, then the percent-decoded fragment. const element = document.getElementById( hash ); if ( element ) { return element; } const decodedHash = this.percentDecodeFragment( hash ); if ( !decodedHash ) { // decodedHash can return null, calling getElementById would cast it to a string return null; } return document.getElementById( decodedHash ); }, /** * Percent-decode a string, as found in a URL hash fragment. * * Implements the percent-decode method as defined in * https://url.spec.whatwg.org/#percent-decode. * * URLSearchParams implements https://url.spec.whatwg.org/#concept-urlencoded-parser * which performs a '+' to ' ' substitution before running percent-decode. * * To get the desired behaviour we percent-encode any '+' in the fragment * to effectively expose the percent-decode implementation. * * @param {string} text Text to decode * @return {string|null} Decoded text, null if decoding failed */ percentDecodeFragment: function ( text ) { const params = new URLSearchParams( 'q=' + text // Query string param decoding replaces '+' with ' ' before doing the // percent_decode, so encode '+' to prevent this. .replace( /\+/g, '%2B' ) // Query strings are split on '&' and then '=' so encode these too. .replace( /&/g, '%26' ) .replace( /=/g, '%3D' ) ); return params.get( 'q' ); }, /** * Return a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. If `immediate` is passed, trigger the function on the * leading edge, instead of the trailing. * * Ported from Underscore.js 1.5.2, Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud * and Investigative Reporters & Editors, distributed under the MIT license, from * <https://github.com/jashkenas/underscore/blob/1.5.2/underscore.js#L689>. * * @since 1.34 * @param {Function} func Function to debounce * @param {number} [wait=0] Wait period in milliseconds * @param {boolean} [immediate] Trigger on leading edge * @return {Function} Debounced function */ debounce: function ( func, wait, immediate ) { // Old signature (wait, func). if ( typeof func === 'number' ) { const tmpWait = wait; wait = func; func = tmpWait; } let timeout; return function () { const context = this, args = arguments, later = function () { timeout = null; if ( !immediate ) { func.apply( context, args ); } }; if ( immediate && !timeout ) { func.apply( context, args ); } if ( !timeout || wait ) { clearTimeout( timeout ); timeout = setTimeout( later, wait ); } }; }, /** * Return a function, that, when invoked, will only be triggered at most once * during a given window of time. If called again during that window, it will * wait until the window ends and then trigger itself again. * * As it's not knowable to the caller whether the function will actually run * when the wrapper is called, return values from the function are entirely * discarded. * * Ported from OOUI. * * @param {Function} func Function to throttle * @param {number} wait Throttle window length, in milliseconds * @return {Function} Throttled function */ throttle: function ( func, wait ) { let context, args, timeout, previous = Date.now() - wait; const run = function () { timeout = null; previous = Date.now(); func.apply( context, args ); }; return function () { // Check how long it's been since the last time the function was // called, and whether it's more or less than the requested throttle // period. If it's less, run the function immediately. If it's more, // set a timeout for the remaining time -- but don't replace an // existing timeout, since that'd indefinitely prolong the wait. const remaining = Math.max( wait - ( Date.now() - previous ), 0 ); context = this; args = arguments; if ( !timeout ) { // If time is up, do setTimeout( run, 0 ) so the function // always runs asynchronously, just like Promise#then . timeout = setTimeout( run, remaining ); } }; }, /** * Encode page titles in a way that matches `wfUrlencode` in PHP. * * This is important both for readability and consistency in the user experience, * as well as for caching. If URLs are not formatted in the canonical way, they * may be subject to drastically shorter cache durations and/or miss automatic * purging after edits, thus leading to stale content being served from a * non-canonical URL. * * @method * @param {string} str String to be encoded. * @return {string} Encoded string */ wikiUrlencode: mw.internalWikiUrlencode, /** * Get the URL to a given local wiki page name. * * @param {string|null} [pageName=wgPageName] Page name * @param {Object} [params] A mapping of query parameter names to values, * e.g. `{ action: 'edit' }` * @return {string} URL, relative to `wgServer`. */ getUrl: function ( pageName, params ) { let url, query, fragment, title = typeof pageName === 'string' ? pageName : mw.config.get( 'wgPageName' ); // Find any fragment const fragmentIdx = title.indexOf( '#' ); if ( fragmentIdx !== -1 ) { fragment = title.slice( fragmentIdx + 1 ); // Exclude the fragment from the page name title = title.slice( 0, fragmentIdx ); } // Produce query string if ( params ) { query = $.param( params ); } if ( !title && fragment ) { // If only a fragment was given, make a fragment-only link (T288415) url = ''; } else if ( query ) { url = title ? util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query : util.wikiScript() + '?' + query; } else { // Specify a function as the replacement, // so that "$" characters in title are not interpreted. url = mw.config.get( 'wgArticlePath' ) .replace( '$1', () => util.wikiUrlencode( title ) ); } // Append the encoded fragment if ( fragment ) { url += '#' + util.escapeIdForLink( fragment ); } return url; }, /** * Get URL to a MediaWiki entry point. * * Similar to `wfScript()` in PHP. * * @since 1.18 * @param {string} [str="index"] Name of entry point (e.g. 'index' or 'api') * @return {string} URL to the script file (e.g. `/w/api.php`) */ wikiScript: function ( str ) { if ( !str || str === 'index' ) { return mw.config.get( 'wgScript' ); } else if ( str === 'load' ) { return config.LoadScript; } else { return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php'; } }, /** * Append a new style block to the head and return the CSSStyleSheet object. * * To access the `<style>` element, reference `sheet.ownerNode`, or call * the {@link mw.loader.addStyleTag} method directly. * * This function returns the CSSStyleSheet object for convenience with features * that are managed at that level, such as toggling of styles: * ``` * var sheet = util.addCSS( '.foobar { display: none; }' ); * $( '#myButton' ).click( function () { * // Toggle the sheet on and off * sheet.disabled = !sheet.disabled; * } ); * ``` * * See also [MDN: CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet). * * @param {string} text CSS to be appended * @return {CSSStyleSheet} The sheet object */ addCSS: function ( text ) { const s = mw.loader.addStyleTag( text ); return s.sheet; }, /** * Get the value for a given URL query parameter. * * @example * mw.util.getParamValue( 'foo', '/?foo=x' ); // "x" * mw.util.getParamValue( 'foo', '/?foo=' ); // "" * mw.util.getParamValue( 'foo', '/' ); // null * * @param {string} param The parameter name. * @param {string} [url=location.href] URL to search through, defaulting to the current browsing location. * @return {string|null} Parameter value, or null if parameter was not found. */ getParamValue: function ( param, url ) { // Get last match, stop at hash const re = new RegExp( '^[^#]*[&?]' + util.escapeRegExp( param ) + '=([^&#]*)' ), m = re.exec( url !== undefined ? url : location.href ); if ( m ) { // Beware that decodeURIComponent is not required to understand '+' // by spec, as encodeURIComponent does not produce it. try { return decodeURIComponent( m[ 1 ].replace( /\+/g, '%20' ) ); } catch ( e ) { // catch URIError if parameter is invalid UTF-8 // due to malformed or double-decoded values (T106244), // e.g. "Autom%F3vil" instead of "Autom%C3%B3vil". } } return null; }, /** * Get the value for an array query parameter, combined according to similar rules as PHP uses. * Currently this does not handle associative or multi-dimensional arrays, but that may be * improved in the future. * * @example * mw.util.getArrayParam( 'foo', new URLSearchParams( '?foo[0]=a&foo[1]=b' ) ); // [ 'a', 'b' ] * mw.util.getArrayParam( 'foo', new URLSearchParams( '?foo[]=a&foo[]=b' ) ); // [ 'a', 'b' ] * mw.util.getArrayParam( 'foo', new URLSearchParams( '?foo=a' ) ); // null * * @param {string} param The parameter name. * @param {URLSearchParams} [params] Parsed URL parameters to search through, defaulting to the current browsing location. * @return {string[]|null} Parameter value, or null if parameter was not found. */ getArrayParam: function ( param, params ) { const paramRe = new RegExp( '^' + util.escapeRegExp( param ) + '\\[(\\d*)\\]$' ); if ( !params ) { params = new URLSearchParams( location.search ); } const arr = []; params.forEach( ( v, k ) => { const paramMatch = k.match( paramRe ); if ( paramMatch ) { let i = paramMatch[ 1 ]; if ( i === '' ) { // If no explicit index, append at the end i = arr.length; } arr[ i ] = v; } } ); return arr.length ? arr : null; }, /** * The content wrapper of the skin (`.mw-body`, for example). * * Populated on document ready. To use this property, * wait for `$.ready` and be sure to have a module dependency on * `mediawiki.util` which will ensure * your document ready handler fires after initialization. * * Because of the lazy-initialised nature of this property, * you're discouraged from using it. * * If you need just the wikipage content (not any of the * extra elements output by the skin), use `$( '#mw-content-text' )` * instead. Or listen to {@link event:'wikipage.content' wikipage.content} * which will allow your code to re-run when the page changes (e.g. live preview * or re-render after ajax save). * * @type {jQuery} */ $content: null, /** * Hide a portlet. * * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal') */ hidePortlet: function ( portletId ) { const portlet = document.getElementById( portletId ); if ( portlet ) { portlet.classList.add( 'emptyPortlet' ); } }, /** * Whether a portlet is visible. * * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal') * @return {boolean} */ isPortletVisible: function ( portletId ) { const portlet = document.getElementById( portletId ); return portlet && !portlet.classList.contains( 'emptyPortlet' ); }, /** * Reveal a portlet if it is hidden. * * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal') */ showPortlet: function ( portletId ) { const portlet = document.getElementById( portletId ); if ( portlet ) { portlet.classList.remove( 'emptyPortlet' ); } }, /** * Clears the entire subtitle if present in the page. Used for refreshing subtitle * after edit with response from parse API. */ clearSubtitle: function () { const subtitle = document.getElementById( 'mw-content-subtitle' ); if ( subtitle ) { subtitle.innerHTML = ''; } }, /** * Create a message box element. Callers are responsible for ensuring suitable Codex styles * have been added to the page e.g. mediawiki.codex.messagebox.styles. * * @since 1.43 * @param {string|Element} textOrElement text or node. * @param {string} [type] defaults to notice. * @param {boolean} [inline] whether the notice should be inline. * @return {Element} */ messageBox: function ( textOrElement, type = 'notice', inline = false ) { const msgBoxElement = document.createElement( 'div' ); msgBoxElement.classList.add( 'cdx-message' ); if ( [ 'error', 'warning', 'success', 'notice' ].indexOf( type ) > -1 ) { // The following CSS classes are used here: // * cdx-message--notice // * cdx-message--warning // * cdx-message--error msgBoxElement.classList.add( `cdx-message--${ type }` ); } msgBoxElement.classList.add( inline ? 'cdx-message--inline' : 'cdx-message--block' ); msgBoxElement.setAttribute( 'aria-live', 'polite' ); const iconElement = document.createElement( 'span' ); iconElement.classList.add( 'cdx-message__icon' ); const contentElement = document.createElement( 'div' ); contentElement.classList.add( 'cdx-message__content' ); if ( typeof textOrElement === 'string' ) { contentElement.textContent = textOrElement; } else { contentElement.appendChild( textOrElement ); } msgBoxElement.appendChild( iconElement ); msgBoxElement.appendChild( contentElement ); return msgBoxElement; }, /** * Add content to the subtitle of the skin. * * @param {HTMLElement|string} nodeOrHTMLString */ addSubtitle: function ( nodeOrHTMLString ) { const subtitle = document.getElementById( 'mw-content-subtitle' ); if ( subtitle ) { if ( typeof nodeOrHTMLString === 'string' ) { subtitle.innerHTML += nodeOrHTMLString; } else { subtitle.appendChild( nodeOrHTMLString ); } } else { throw new Error( 'This skin does not support additions to the subtitle.' ); } }, /** * Creates a detached portlet Element in the skin with no elements. * * @example * // Create a portlet with 2 menu items that is styled as a dropdown in certain skins. * const p = mw.util.addPortlet( 'p-myportlet', 'My label', '#p-cactions' ); * mw.util.addPortletLink( 'p-myportlet', '#', 'Link 1' ); * mw.util.addPortletLink( 'p-myportlet', '#', 'Link 2' ); * @param {string} id of the new portlet. * @param {string} [label] of the new portlet. * @param {string} [selectorHint] selector of the element the new portlet would like to * be inserted near. Typically the portlet will be inserted after this selector, but in some * skins, the skin may relocate the element when provided to the closest available space. * If this argument is not passed then the caller is responsible for appending the element * to the DOM before using addPortletLink. * To add a portlet in an exact position do not rely on this parameter, instead using the return * element (make sure to also assign the result to a variable), use * ```p.parentNode.appendChild( p );``` * When provided, skins can use the parameter to infer information about how the user intended * the menu to be rendered. For example, in vector and vector-2022 targeting '#p-cactions' will * result in the creation of a dropdown. * @fires Hooks~'util.addPortlet' * @return {HTMLElement|null} will be null if it was not possible to create an portlet with * the required information e.g. the selector given in `selectorHint` parameter could not be resolved * to an existing element in the page. */ addPortlet: function ( id, label, selectorHint ) { const portlet = document.createElement( 'div' ); // These classes should be kept in sync with includes/skins/components/SkinComponentMenu.php. // eslint-disable-next-line mediawiki/class-doc portlet.classList.add( 'mw-portlet', 'mw-portlet-' + id, 'emptyPortlet', // Additional class is added to allow skins to track portlets added via this mechanism. 'mw-portlet-js' ); portlet.id = id; if ( label ) { const labelNode = document.createElement( 'label' ); labelNode.textContent = label; portlet.appendChild( labelNode ); } const listWrapper = document.createElement( 'div' ); const list = document.createElement( 'ul' ); listWrapper.appendChild( list ); portlet.appendChild( listWrapper ); if ( selectorHint ) { let referenceNode; try { referenceNode = document.querySelector( selectorHint ); } catch ( e ) { // CSS selector not supported by browser. } if ( referenceNode ) { const parentNode = referenceNode.parentNode; parentNode.insertBefore( portlet, referenceNode ); } else { return null; } } /** * Fires when a portlet is successfully created. * * @event ~'util.addPortlet' * @memberof Hooks * @param {HTMLElement} portlet the portlet that was created. * @param {string|null} selectorHint the css selector used to append to the DOM. * * @example * mw.hook( 'util.addPortlet' ).add( ( p ) => { * p.style.border = 'solid 1px black'; * } ); */ mw.hook( 'util.addPortlet' ).fire( portlet, selectorHint ); return portlet; }, /** * Add a link to a portlet menu on the page. * * The portlets that are supported include: * * - p-cactions (Content actions) * - p-personal (Personal tools) * - p-navigation (Navigation) * - p-tb (Toolbox) * - p-associated-pages (For namespaces and special page tabs on supported skins) * - p-namespaces (For namespaces on legacy skins) * * Additional menus can be discovered through the following code: * ```$('.mw-portlet').toArray().map((el) => el.id);``` * * Menu availability varies by skin, wiki, and current page. * * The first three parameters are required, the others are optional and * may be null. Though providing an id and tooltip is recommended. * * By default, the new link will be added to the end of the menu. To * add the link before an existing item, pass the DOM node or a CSS selector * for that item, e.g. `'#foobar'` or `document.getElementById( 'foobar' )`. * ``` * mw.util.addPortletLink( * 'p-tb', 'https://www.mediawiki.org/', * 'mediawiki.org', 't-mworg', 'Go to mediawiki.org', 'm', '#t-print' * ); * * var node = mw.util.addPortletLink( * 'p-tb', * mw.util.getUrl( 'Special:Example' ), * 'Example' * ); * $( node ).on( 'click', function ( e ) { * console.log( 'Example' ); * e.preventDefault(); * } ); * ``` * * Remember that to call this inside a user script, you may have to ensure the * `mediawiki.util` is loaded first: * ``` * $.when( mw.loader.using( [ 'mediawiki.util' ] ), $.ready ).then( function () { * mw.util.addPortletLink( 'p-tb', 'https://www.mediawiki.org/', 'mediawiki.org' ); * } ); * ``` * * @param {string} portletId ID of the target portlet (e.g. 'p-cactions' or 'p-personal') * @param {string} href Link URL * @param {string} text Link text * @param {string} [id] ID of the list item, should be unique and preferably have * the appropriate prefix ('ca-', 'pt-', 'n-' or 't-') * @param {string} [tooltip] Text to show when hovering over the link, without accesskey suffix * @param {string} [accesskey] Access key to activate this link. One character only, * avoid conflicts with other links. Use `$( '[accesskey=x]' )` in the console to * see if 'x' is already used. * @param {HTMLElement|jQuery|string} [nextnode] Element that the new item should be added before. * Must be another item in the same list, it will be ignored otherwise. * Can be specified as DOM reference, as jQuery object, or as CSS selector string. * @fires Hooks~'util.addPortletLink' * @return {HTMLElement|null} The added list item, or null if no element was added. */ addPortletLink: function ( portletId, href, text, id, tooltip, accesskey, nextnode ) { if ( !portletId ) { // Avoid confusing id="undefined" lookup return null; } const portlet = document.getElementById( portletId ); if ( !portlet ) { // Invalid portlet ID return null; } // Setup the anchor tag and set any the properties const link = document.createElement( 'a' ); link.href = href; let linkChild = document.createTextNode( text ); let i = portletLinkOptions[ 'text-wrapper' ].length; // Wrap link using text-wrapper option if provided // Iterate backward since the wrappers are declared from outer to inner, // and we build it up from the inside out. while ( i-- ) { const wrapper = portletLinkOptions[ 'text-wrapper' ][ i ]; const wrapperElement = document.createElement( wrapper.tag ); if ( wrapper.attributes ) { $( wrapperElement ).attr( wrapper.attributes ); } wrapperElement.appendChild( linkChild ); linkChild = wrapperElement; } link.appendChild( linkChild ); if ( tooltip ) { link.title = tooltip; } if ( accesskey ) { link.accessKey = accesskey; } // Unhide portlet if it was hidden before util.showPortlet( portletId ); const item = $( '<li>' ).append( link )[ 0 ]; // mw-list-item-js distinguishes portlet links added via javascript and the server item.className = 'mw-list-item mw-list-item-js'; if ( id ) { item.id = id; } // Select the first (most likely only) unordered list inside the portlet let ul = portlet.tagName.toLowerCase() === 'ul' ? portlet : portlet.querySelector( 'ul' ); if ( !ul ) { // If it didn't have an unordered list yet, create one ul = document.createElement( 'ul' ); const portletDiv = portlet.querySelector( 'div' ); if ( portletDiv ) { // Support: Legacy skins have a div (such as div.body or div.pBody). // Append the <ul> to that. portletDiv.appendChild( ul ); } else { // Append it to the portlet directly portlet.appendChild( ul ); } } let next; if ( nextnode && ( typeof nextnode === 'string' || nextnode.nodeType || nextnode.jquery ) ) { // eslint-disable-next-line no-jquery/variable-pattern nextnode = $( ul ).find( nextnode ); if ( nextnode.length === 1 && nextnode[ 0 ].parentNode === ul ) { // Insertion point: Before nextnode nextnode.before( item ); next = true; } // Else: Invalid nextnode value (no match, more than one match, or not a direct child) // Else: Invalid nextnode type } if ( !next ) { // Insertion point: End of list (default) ul.appendChild( item ); } // Update tooltip for the access key after inserting into DOM // to get a localized access key label (T69946). if ( accesskey ) { $( link ).updateTooltipAccessKeys(); } /** * Fires when a portlet link is successfully created. * * @event ~'util.addPortletLink' * @memberof Hooks * @param {HTMLElement} item the portlet link that was created. * @param {Object} information about the item include id. * * @example * mw.hook( 'util.addPortletLink' ).add( ( link ) => { * const span = $( '<span class="icon">' ); * link.appendChild( span ); * } ); */ mw.hook( 'util.addPortletLink' ).fire( item, { id: id } ); return item; }, /** * Validate a string as representing a valid e-mail address. * * This validation is based on the HTML5 specification. * * @example * mw.util.validateEmail( "me@example.org" ) === true; * * @param {string} email E-mail address * @return {boolean|null} True if valid, false if invalid, null if `email` was empty. */ validateEmail: function ( email ) { if ( email === '' ) { return null; } // HTML5 defines a string as valid e-mail address if it matches // the ABNF: // 1 * ( atext / "." ) "@" ldh-str 1*( "." ldh-str ) // With: // - atext : defined in RFC 5322 section 3.2.3 // - ldh-str : defined in RFC 1034 section 3.5 // // (see STD 68 / RFC 5234 https://tools.ietf.org/html/std68) // First, define the RFC 5322 'atext' which is pretty easy: // atext = ALPHA / DIGIT / ; Printable US-ASCII // "!" / "#" / ; characters not including // "$" / "%" / ; specials. Used for atoms. // "&" / "'" / // "*" / "+" / // "-" / "/" / // "=" / "?" / // "^" / "_" / // "`" / "{" / // "|" / "}" / // "~" const rfc5322Atext = 'a-z0-9!#$%&\'*+\\-/=?^_`{|}~'; // Next define the RFC 1034 'ldh-str' // <domain> ::= <subdomain> | " " // <subdomain> ::= <label> | <subdomain> "." <label> // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> // <let-dig-hyp> ::= <let-dig> | "-" // <let-dig> ::= <letter> | <digit> const rfc1034LdhStr = 'a-z0-9\\-'; const html5EmailRegexp = new RegExp( // start of string '^' + // User part which is liberal :p '[' + rfc5322Atext + '\\.]+' + // 'at' '@' + // Domain first part '[' + rfc1034LdhStr + ']+' + // Optional second part and following are separated by a dot '(?:\\.[' + rfc1034LdhStr + ']+)*' + // End of string '$', // RegExp is case insensitive 'i' ); return ( email.match( html5EmailRegexp ) !== null ); }, /** * Whether a string is a valid IPv4 address or not. * * Based on \Wikimedia\IPUtils::isIPv4 in PHP. * * @example * // Valid * mw.util.isIPv4Address( '80.100.20.101' ); * mw.util.isIPv4Address( '192.168.1.101' ); * * // Invalid * mw.util.isIPv4Address( '192.0.2.0/24' ); * mw.util.isIPv4Address( 'hello' ); * * @param {string} address * @param {boolean} [allowBlock=false] * @return {boolean} */ isIPv4Address: function ( address, allowBlock ) { if ( typeof address !== 'string' ) { return false; } const RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])'; const RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE; const block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : ''; return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) ); }, /** * Whether a string is a valid IPv6 address or not. * * Based on \Wikimedia\IPUtils::isIPv6 in PHP. * * @example * // Valid * mw.util.isIPv6Address( '2001:db8:a:0:0:0:0:0' ); * mw.util.isIPv6Address( '2001:db8:a::' ); * * // Invalid * mw.util.isIPv6Address( '2001:db8:a::/32' ); * mw.util.isIPv6Address( 'hello' ); * * @param {string} address * @param {boolean} [allowBlock=false] * @return {boolean} */ isIPv6Address: function ( address, allowBlock ) { if ( typeof address !== 'string' ) { return false; } const block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : ''; let RE_IPV6_ADD = '(?:' + // starts with "::" (including "::") ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' + '|' + // ends with "::" (except "::") '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' + '|' + // contains no "::" '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' + ')'; if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) { return true; } // contains one "::" in the middle (single '::' check below) RE_IPV6_ADD = '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}'; return ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) && /::/.test( address ) && !/::.*::/.test( address ) ); }, /** * Check whether a string is a valid IP address. * * @since 1.25 * @param {string} address String to check * @param {boolean} [allowBlock=false] If a block of IPs should be allowed * @return {boolean} */ isIPAddress: function ( address, allowBlock ) { return util.isIPv4Address( address, allowBlock ) || util.isIPv6Address( address, allowBlock ); }, /** * @typedef {Object} ResizeableThumbnailUrl * @property {string} name File name (same format as Title.getMainText()). * @property {number} [width] Thumbnail width, in pixels. Null when the file is not * a thumbnail. * @property {function(number):string} [resizeUrl] A function that takes a width * parameter and returns a thumbnail URL (URL-encoded) with that width. The width * parameter must be smaller than the width of the original image (or equal to it; that * only works if MediaHandler::mustRender returns true for the file). Null when the * file in the original URL is not a thumbnail. * On wikis with $wgGenerateThumbnailOnParse set to true, this will fall back to using * Special:Redirect which is less efficient. Otherwise, it is a direct thumbnail URL. */ /** * Parse the URL of an image uploaded to MediaWiki, or a thumbnail for such an image, * and return the image name, thumbnail size and a template that can be used to resize * the image. * * @param {string} url URL to parse (URL-encoded) * @return {ResizeableThumbnailUrl|null} null if the URL is not a valid MediaWiki * image/thumbnail URL. */ parseImageUrl: function ( url ) { let name, decodedName, width, urlTemplate; // thumb.php-generated thumbnails // thumb.php?f=<name>&w[idth]=<width>[px] if ( /thumb\.php/.test( url ) ) { decodedName = mw.util.getParamValue( 'f', url ); name = encodeURIComponent( decodedName ); width = mw.util.getParamValue( 'width', url ) || mw.util.getParamValue( 'w', url ); urlTemplate = url.replace( /([&?])w(?:idth)?=[^&]+/g, '' ) + '&width={width}'; } else { const regexes = [ // Thumbnails // /<hash prefix>/<name>/[<options>-]<width>-<name*>[.<ext>] // where <name*> could be the filename, 'thumbnail.<ext>' (for long filenames) // or the base-36 SHA1 of the filename. /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)\/(?:[^\s/]+-)?(\d+)px-(?:\1|thumbnail|[a-z\d]{31})(\.[^\s/]+)?$/, // Full size images // /<hash prefix>/<name> /\/[\da-f]\/[\da-f]{2}\/([^\s/]+)$/, // Thumbnails in non-hashed upload directories // /<name>/[<options>-]<width>-<name*>[.<ext>] /\/([^\s/]+)\/(?:[^\s/]+-)?(\d+)px-(?:\1|thumbnail|[a-z\d]{31})[^\s/]*$/, // Full-size images in non-hashed upload directories // /<name> /\/([^\s/]+)$/ ]; for ( let i = 0; i < regexes.length; i++ ) { const match = url.match( regexes[ i ] ); if ( match ) { name = match[ 1 ]; decodedName = decodeURIComponent( name ); width = match[ 2 ] || null; break; } } } if ( name ) { if ( width !== null ) { width = parseInt( width, 10 ) || null; } if ( config.GenerateThumbnailOnParse ) { // The wiki cannot generate thumbnails on demand. Use a special page - this means // an extra redirect and PHP request, but it will generate the thumbnail if it does // not exist. urlTemplate = mw.util.getUrl( 'Special:Redirect/file/' + decodedName, { width: '{width}' } ) // getUrl urlencodes the template variable, fix that .replace( '%7Bwidth%7D', '{width}' ); } else if ( width && !urlTemplate ) { // Javascript does not expose regexp capturing group indexes, and the width // part could in theory also occur in the filename so hide that first. const strippedUrl = url.replace( name, '{name}' ) .replace( name, '{name}' ) .replace( width + 'px-', '{width}px-' ); urlTemplate = strippedUrl.replace( /\{name\}/g, name ); } return { name: decodedName.replace( /_/g, ' ' ), width, resizeUrl: urlTemplate ? function ( w ) { return urlTemplate.replace( '{width}', w ); } : null }; } return null; }, /** * Escape string for safe inclusion in regular expression. * * The following characters are escaped: * * \ { } ( ) | . ? * + - ^ $ [ ] * * @since 1.26; moved to mw.util in 1.34 * @param {string} str String to escape * @return {string} Escaped string */ escapeRegExp: function ( str ) { // eslint-disable-next-line no-useless-escape return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); }, /** * Convert an IP into a verbose, uppercase, normalized form. * * Both IPv4 and IPv6 addresses are trimmed. Additionally, * IPv6 addresses in octet notation are expanded to 8 words; * IPv4 addresses have leading zeros, in each octet, removed. * * This functionality has been adapted from \Wikimedia\IPUtils::sanitizeIP() * * @param {string} ip IP address in quad or octet form (CIDR or not). * @return {string|null} */ sanitizeIP: function ( ip ) { if ( typeof ip !== 'string' ) { return null; } ip = ip.trim(); if ( ip === '' ) { return null; } if ( !this.isIPAddress( ip, true ) ) { return ip; } if ( this.isIPv4Address( ip, true ) ) { return ip.replace( /(^|\.)0+(\d)/g, '$1$2' ); } ip = ip.toUpperCase(); const abbrevPos = ip.indexOf( '::' ); if ( abbrevPos !== -1 ) { const CIDRStart = ip.indexOf( '/' ); const addressEnd = ( CIDRStart !== -1 ) ? CIDRStart - 1 : ip.length - 1; let repeatStr, extra, pad; if ( abbrevPos === 0 ) { repeatStr = '0:'; extra = ip === '::' ? '0' : ''; pad = 9; } else if ( abbrevPos === addressEnd - 1 ) { repeatStr = ':0'; extra = ''; pad = 9; } else { repeatStr = ':0'; extra = ':'; pad = 8; } const count = pad - ( ip.split( ':' ).length - 1 ); ip = ip.replace( '::', repeatStr.repeat( count ) + extra ); } return ip.replace( /(^|:)0+(([0-9A-Fa-f]{1,4}))/g, '$1$2' ); }, /** * Prettify an IP for display to end users. * * This will make it more compact and lower-case. * * This functionality has been adapted from \Wikimedia\IPUtils::prettifyIP() * * @param {string} ip IP address in quad or octet form (CIDR or not). * @return {string|null} */ prettifyIP: function ( ip ) { ip = this.sanitizeIP( ip ); if ( ip === null ) { return null; } if ( this.isIPv6Address( ip, true ) ) { let cidr, replaceZeros; if ( ip.indexOf( '/' ) !== -1 ) { const ipCidrSplit = ip.split( '/', 2 ); ip = ipCidrSplit[ 0 ]; cidr = ipCidrSplit[ 1 ]; } else { cidr = ''; } const matches = ip.match( /(?:^|:)0(?::0)+(?:$|:)/g ); if ( matches ) { replaceZeros = matches[ 0 ]; for ( let i = 1; i < matches.length; i++ ) { if ( matches[ i ].length > replaceZeros.length ) { replaceZeros = matches[ i ]; } } } ip = ip.replace( replaceZeros, '::' ); if ( cidr !== '' ) { ip = ip.concat( '/', cidr ); } ip = ip.toLowerCase(); } return ip; }, /** * Checks if the given username matches $wgAutoCreateTempUser. * * This functionality has been adapted from MediaWiki\User\TempUser\Pattern::isMatch() * * @param {string|null} username * @return {boolean} */ isTemporaryUser: function ( username ) { // Just return early if temporary accounts are not known about. if ( !config.AutoCreateTempUser.enabled && !config.AutoCreateTempUser.known ) { return false; } if ( username === null ) { return false; } /** @type {string|string[]} */ let matchPatterns = config.AutoCreateTempUser.matchPattern; if ( typeof matchPatterns === 'string' ) { matchPatterns = [ matchPatterns ]; } else if ( matchPatterns === null ) { matchPatterns = [ config.AutoCreateTempUser.genPattern ]; } for ( let i = 0; i < matchPatterns.length; i++ ) { const autoCreateUserMatchPattern = matchPatterns[ i ]; // Check each match pattern, and if any matches then return a match. const position = autoCreateUserMatchPattern.indexOf( '$1' ); // '$1' was not found in autoCreateUserMatchPattern if ( position === -1 ) { return false; } const prefix = autoCreateUserMatchPattern.slice( 0, position ); const suffix = autoCreateUserMatchPattern.slice( position + '$1'.length ); let match = true; if ( prefix !== '' ) { match = ( username.indexOf( prefix ) === 0 ); } if ( match && suffix !== '' ) { match = ( username.slice( -suffix.length ) === suffix ) && ( username.length >= prefix.length + suffix.length ); } if ( match ) { return true; } } // No match patterns matched the username, so the given username is not a temporary user. return false; }, /** * Determine if an input string represents a value of infinity. * This is used when testing for infinity in the context of expiries, * such as watchlisting, page protection, and block expiries. * * @param {string|null} str * @return {boolean} * @stable */ isInfinity: function ( str ) { return infinityValues.indexOf( str ) !== -1; } }; /** * Initialisation of mw.util.$content * * @ignore */ function init() { // The preferred standard is class "mw-body". // You may also use class "mw-body mw-body-primary" if you use // mw-body in multiple locations. Or class "mw-body-primary" if // you use mw-body deeper in the DOM. const content = document.querySelector( '.mw-body-primary' ) || document.querySelector( '.mw-body' ) || // If the skin has no such class, fall back to the parser output document.querySelector( '#mw-content-text' ) || // Should never happen..., except if the skin is still in development. document.body; util.$content = $( content ); } // Backwards-compatible alias for mediawiki.RegExp module. // @deprecated since 1.34 mw.RegExp = {}; mw.log.deprecate( mw.RegExp, 'escape', util.escapeRegExp, 'Use mw.util.escapeRegExp() instead.', 'mw.RegExp.escape' ); if ( window.QUnit ) { // Not allowed outside unit tests util.setOptionsForTest = function ( opts ) { config = !opts ? require( './config.json' ) : Object.assign( {}, config, opts ); }; util.init = init; } else { $( init ); } mw.util = util; module.exports = util;
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка