Файловый менеджер - Редактировать - /var/www/html/mediawiki.editRecovery.zip
Ðазад
PK ! V0Z2� � LoadNotification.jsnu �Iw�� /** * @class mw.widgets.EditRecovery.LoadNotification * @constructor * @extends OO.ui.Widget * @ignore * @param {Object} config * @param {boolean} config.differentRev Whether to display the 'different revision' warning. */ const LoadNotification = function mwWidgetsEditRecoveryLoadNotification( config ) { LoadNotification.super.call( this, {} ); this.recoverButton = new OO.ui.ButtonWidget( { label: mw.msg( 'edit-recovery-loaded-recover' ), flags: [ 'progressive' ] } ); this.discardButton = new OO.ui.ButtonWidget( { label: mw.msg( 'edit-recovery-loaded-discard' ), framed: false, flags: [ 'destructive' ] } ); const $buttons = $( '<div>' ) .addClass( 'mw-EditRecovery-LoadNotification-buttons' ) .append( this.recoverButton.$element, this.discardButton.$element ); let differentRev = null; let differentRevSave = null; if ( config.differentRev ) { differentRev = mw.message( 'edit-recovery-loaded-message-different-rev' ).parse(); differentRevSave = mw.config.get( 'wgEditSubmitButtonLabelPublish' ) ? mw.message( 'edit-recovery-loaded-message-different-rev-publish' ).parse() : mw.message( 'edit-recovery-loaded-message-different-rev-save' ).parse(); } this.$element.append( mw.message( 'edit-recovery-loaded-message' ).escaped(), mw.message( 'word-separator' ).parse(), differentRev, mw.message( 'word-separator' ).parse(), differentRevSave, $buttons ); }; OO.inheritClass( LoadNotification, OO.ui.Widget ); /** * @ignore * @return {mw.Notification} */ LoadNotification.prototype.getNotification = function () { return mw.notification.notify( this.$element, { title: mw.msg( 'edit-recovery-loaded-title' ), autoHide: false } ); }; /** * @ignore * @return {OO.ui.ButtonWidget} */ LoadNotification.prototype.getRecoverButton = function () { return this.recoverButton; }; /** * @ignore * @return {OO.ui.ButtonWidget} */ LoadNotification.prototype.getDiscardButton = function () { return this.discardButton; }; module.exports = LoadNotification; PK ! M�^�x x styles.lessnu �Iw�� @import 'mediawiki.skin.variables.less'; .mw-EditRecovery-LoadNotification-buttons { margin-top: @size-50; text-align: center; display: flex; flex-wrap: wrap; // Allow long phrases in buttons to wrap to multiple lines rather than extend outside the notification box. .oo-ui-buttonElement > .oo-ui-buttonElement-button { white-space: normal; } .oo-ui-buttonWidget, .oo-ui-buttonWidget:last-child { // Remove trailing margins (because we handle horizontal spacing above), and add a top // margin to keep separation between the buttons and between the buttons and the text above. margin: @size-50 auto 0 auto; } } PK ! �LȌ�* �* edit.jsnu �Iw�� /** * In-progress edit recovery for action=edit */ 'use strict'; const storage = require( './storage.js' ); const LoadNotification = require( './LoadNotification.js' ); const pageName = mw.config.get( 'wgPageName' ); const section = $( 'input[name="wpSection"]' ).val() || null; const inputFields = {}; const fieldNamePrefix = 'field_'; let originalData = {}; let changeDebounceTimer = null; // Number of miliseconds to debounce form input. const debounceTime = 5000; // This module is loaded for every edit form, but not all should have Edit Recovery functioning. const wasPosted = mw.config.get( 'wgEditRecoveryWasPosted' ); const isUndo = $( 'input[name="wpUndoAfter"]' ).length > 0; const isOldRevision = $( 'input[name="oldid"]' ).val() > 0; const isConflict = mw.config.get( 'wgEditMessage' ) === 'editconflict'; const useEditRecovery = !isUndo && !isOldRevision && !isConflict; if ( useEditRecovery ) { mw.hook( 'wikipage.editform' ).add( onLoadHandler ); } else { // Always remove the data-saved flag when editing without Edit Recovery. // It may have been set by a previous editing session (within 5 minutes) that did use ER. mw.storage.session.remove( 'EditRecovery-data-saved' ); } const windowManager = OO.ui.getWindowManager(); windowManager.addWindows( [ new mw.widgets.AbandonEditDialog() ] ); function onLoadHandler( $editForm ) { mw.hook( 'wikipage.editform' ).remove( onLoadHandler ); // Monitor all text-entry inputs for changes/typing. const inputsToMonitorSelector = 'textarea, select, input:not([type="hidden"], [type="submit"])'; const $inputsToMonitor = $editForm.find( inputsToMonitorSelector ); $inputsToMonitor.each( ( _i, field ) => { if ( field.classList.contains( 'oo-ui-inputWidget-input' ) ) { try { inputFields[ field.name ] = OO.ui.infuse( field.closest( '.oo-ui-widget' ) ); } catch ( e ) { // Ignore any non-infusable widget because we won't be able to set its value. } } else { inputFields[ field.name ] = field; } } ); // Save the contents of all of those, as well as the following hidden inputs. const inputsToSaveNames = [ 'wpSection', 'editRevId', 'oldid', 'parentRevId', 'format', 'model' ]; const $inputsToSave = $editForm.find( '[name="' + inputsToSaveNames.join( '"], [name="' ) + '"]' ); $inputsToSave.each( ( _i, field ) => { inputFields[ field.name ] = field; } ); // Store the original data for later comparing to the data-to-save. Use the defaultValue/defaultChecked in order to // avoid using any data remembered by the browser. Note that we have to be careful to store with the same types as // it will be done later, in order to correctly compare it (e.g. checkboxes as booleans). Object.keys( inputFields ).forEach( ( fieldName ) => { const field = inputFields[ fieldName ]; if ( field.nodeName === 'INPUT' || field.nodeName === 'TEXTAREA' ) { if ( field.type === 'checkbox' ) { // Checkboxes (Minoredit and Watchthis are handled below as they are OOUI widgets). originalData[ fieldNamePrefix + fieldName ] = field.defaultChecked; } else { // Other HTMLInputElements. originalData[ fieldNamePrefix + fieldName ] = field.defaultValue; } } else if ( field.$input !== undefined ) { // OOUI widgets, which may not have been infused by this point. if ( field.$input[ 0 ].type === 'checkbox' ) { // Checkboxes. originalData[ fieldNamePrefix + fieldName ] = field.$input[ 0 ].defaultChecked; } else { // Other OOUI widgets. originalData[ fieldNamePrefix + fieldName ] = field.$input[ 0 ].defaultValue; } } } ); // Set a short-lived (5m / see postEdit.js) localStorage item to indicate which section is being edited. if ( section ) { mw.storage.session.set( pageName + '-editRecoverySection', section, 300 ); } // Open indexedDB database and load any saved data that might be there. storage.openDatabase().then( () => { // Check for and delete any expired data for any page, before loading any saved data for the current page. storage.deleteExpiredData().then( () => { storage.loadData( pageName, section ).then( onLoadData ); } ); } ); // Set up cancel handler to delete data. const cancelButton = OO.ui.infuse( $editForm.find( '#mw-editform-cancel' )[ 0 ] ); cancelButton.on( 'click', () => { windowManager.openWindow( 'abandonedit' ).closed.then( ( data ) => { if ( data && data.action === 'discard' ) { // Note that originalData is used below in onLoadData() but that's always called before this method. // Here we set originalData to null in order to signal to saveFormData() to deleted the stored data. originalData = null; storage.deleteData( pageName, section ).finally( () => { mw.storage.session.remove( pageName + '-editRecoverySection' ); // Release the beforeunload handler from mediawiki.action.edit.editWarning, // per the documentation there $( window ).off( 'beforeunload.editwarning' ); location.href = cancelButton.getHref(); } ); } } ); } ); } function track( metric, value ) { const dbName = mw.config.get( 'wgDBname' ); mw.track( `counter.MediaWiki.edit_recovery.${ metric }.by_wiki.${ dbName }`, value ); } function onLoadData( pageData ) { if ( wasPosted ) { // If this is a POST request, save the current data (e.g. from a preview). saveFormData(); } // If there is data stored, load it into the form. if ( !wasPosted && pageData !== undefined && !isSameAsOriginal( pageData, true ) ) { const loadNotification = new LoadNotification( { differentRev: originalData.field_parentRevId !== pageData.field_parentRevId } ); // statsv: Track the number of times the edit recovery notification is shown. track( 'show', 1 ); const notification = loadNotification.getNotification(); // On 'restore changes'. loadNotification.getRecoverButton().on( 'click', () => { loadData( pageData ); notification.close(); // statsv: Track the number of times the edit recovery data is recovered. track( 'recover', 1 ); } ); // On 'discard changes'. loadNotification.getDiscardButton().on( 'click', () => { loadData( originalData ); storage.deleteData( pageName, section ).then( () => { notification.close(); } ); // statsv: Track the number of times the edit recovery data is discarded. track( 'discard', 1 ); } ); } // Add change handlers. Object.keys( inputFields ).forEach( ( fieldName ) => { const field = inputFields[ fieldName ]; if ( field.nodeName !== undefined && field.nodeName === 'TEXTAREA' ) { field.addEventListener( 'input', fieldChangeHandler ); } else if ( field instanceof OO.ui.Widget ) { field.on( 'change', fieldChangeHandler ); } else { field.addEventListener( 'change', fieldChangeHandler ); } } ); // Also add handlers for when the window is closed or hidden. Saving the data at these points is not guaranteed to // work, but it often does and the save operation is atomic so there's no harm in trying. window.addEventListener( 'beforeunload', saveFormData ); window.addEventListener( 'blur', saveFormData ); /** * Fired after EditRecovery has loaded any recovery data, added event handlers, etc. * * @event ~'editRecovery.loadEnd' * @memberof Hooks * @param {Object} editRecovery * @param {Function} editRecovery.fieldChangeHandler */ mw.hook( 'editRecovery.loadEnd' ).fire( { fieldChangeHandler: fieldChangeHandler } ); } function loadData( pageData ) { Object.keys( inputFields ).forEach( ( fieldName ) => { if ( pageData[ fieldNamePrefix + fieldName ] === undefined ) { return; } const field = inputFields[ fieldName ]; const $field = $( field ); // Set the field value depending on what type of field it is. if ( field instanceof OO.ui.CheckboxInputWidget ) { // OOUI checkbox widgets. field.setSelected( pageData[ fieldNamePrefix + fieldName ] ); } else if ( field instanceof OO.ui.Widget ) { // Other OOUI widgets. field.setValue( pageData[ fieldNamePrefix + fieldName ], field ); } else if ( field.nodeName === 'TEXTAREA' ) { // Textareas (also reset caret location to top). $field.textSelection( 'setContents', pageData[ fieldNamePrefix + fieldName ] ); $field.textSelection( 'setSelection', { start: 0 } ); } else { // Anything else. field.value = pageData[ fieldNamePrefix + fieldName ]; } } ); } function fieldChangeHandler() { clearTimeout( changeDebounceTimer ); changeDebounceTimer = setTimeout( saveFormData, debounceTime ); } /** * Compare a set of form field values to their original values (as at page load time). * * @ignore * @param {Object} pageData The page data to compare to the original. * @param {boolean} [ignoreRevIds=false] Do not use parent revision info when determining similarity. * @return {boolean} */ function isSameAsOriginal( pageData, ignoreRevIds = false ) { for ( const fieldName in inputFields ) { if ( ignoreRevIds && ( fieldName === 'editRevId' || fieldName === 'parentRevId' ) ) { continue; } // Trim trailing whitespace from string fields, to approximate what PHP does when saving. let currentVal = pageData[ fieldNamePrefix + fieldName ]; if ( typeof currentVal === 'string' ) { currentVal = currentVal.replace( /\s+$/, '' ); } let originalVal = originalData[ fieldNamePrefix + fieldName ]; if ( typeof originalVal === 'string' ) { originalVal = originalVal.replace( /\s+$/, '' ); } if ( currentVal !== originalVal ) { return false; } } return true; } function saveFormData() { const pageData = getFormData(); if ( ( originalData === null || isSameAsOriginal( pageData ) ) && !wasPosted ) { // Delete the stored data if there's no change, // or if we've flagged originalData as irrelevant, // or if we can't determine this because this page was POSTed. storage.deleteData( pageName, section ); mw.storage.session.remove( 'EditRecovery-data-saved' ); } else { storage.saveData( pageName, section, pageData ); // Flag the data for deletion in the postEdit handler in ./postEdit.js mw.storage.session.set( 'EditRecovery-data-saved', true, 300 ); } } /** * Get the current form data. * * @ignore * @return {Object} */ function getFormData() { const formData = {}; Object.keys( inputFields ).forEach( ( fieldName ) => { const field = inputFields[ fieldName ]; let newValue = null; if ( !( field instanceof OO.ui.Widget ) && field.nodeName !== undefined && field.nodeName === 'TEXTAREA' ) { // Text areas. newValue = $( field ).textSelection( 'getContents' ); } else if ( field instanceof OO.ui.CheckboxInputWidget ) { // OOUI checkbox widgets. newValue = field.isSelected(); } else if ( field instanceof OO.ui.Widget ) { // Other OOUI widgets. newValue = field.getValue(); } else { // Anything else. newValue = field.value; } formData[ fieldNamePrefix + fieldName ] = newValue; } ); return formData; } PK ! ���v� � storage.jsnu �Iw�� /*! * Common indexedDB-access methods, only for use by the ResourceLoader modules this directory. */ const config = require( './config.json' ); const dbName = mw.config.get( 'wgDBname' ) + '_editRecovery'; const editRecoveryExpiry = config.EditRecoveryExpiry; const objectStoreName = 'unsaved-page-data'; let db = null; // TODO: Document Promise objects as native promises, not jQuery ones. /** * @ignore * @return {jQuery.Promise} Promise which resolves on success */ function openDatabaseLocal() { return new Promise( ( resolve, reject ) => { const schemaNumber = 3; const openRequest = window.indexedDB.open( dbName, schemaNumber ); openRequest.addEventListener( 'upgradeneeded', upgradeDatabase ); openRequest.addEventListener( 'success', ( event ) => { db = event.target.result; resolve(); } ); openRequest.addEventListener( 'error', ( event ) => { reject( 'EditRecovery error: ' + event.target.error ); } ); } ); } /** * @private * @param {Object} versionChangeEvent */ function upgradeDatabase( versionChangeEvent ) { const keyPathParts = [ 'pageName', 'section' ]; let objectStore; db = versionChangeEvent.target.result; if ( !db.objectStoreNames.contains( objectStoreName ) ) { // ObjectStore does not yet exist, create it. objectStore = db.createObjectStore( objectStoreName, { keyPath: keyPathParts } ); } else { // ObjectStore exists, but needs to be upgraded. objectStore = versionChangeEvent.target.transaction.objectStore( objectStoreName ); } // Create indexes if they don't exist. if ( !objectStore.indexNames.contains( 'pageName-section' ) ) { objectStore.createIndex( 'pageName-section', keyPathParts, { unique: true } ); } if ( !objectStore.indexNames.contains( 'expiry' ) ) { objectStore.createIndex( 'expiry', 'expiry' ); } // Delete old indexes. if ( objectStore.indexNames.contains( 'lastModified' ) ) { objectStore.deleteIndex( 'lastModified' ); } if ( objectStore.indexNames.contains( 'expiryDate' ) ) { objectStore.deleteIndex( 'expiryDate' ); } } /** * Load data relating to a specific page and section * * @ignore * @param {string} pageName The current page name (with underscores) * @param {string|null} section The section ID, or null if the whole page is being edited * @return {jQuery.Promise} Promise which resolves with the page data on success, or rejects with an error message. */ function loadData( pageName, section ) { return new Promise( ( resolve, reject ) => { if ( !db ) { reject( 'DB not opened' ); } const transaction = db.transaction( objectStoreName, 'readonly' ); const key = [ pageName, section || '' ]; const findExisting = transaction .objectStore( objectStoreName ) .get( key ); findExisting.addEventListener( 'success', () => { resolve( findExisting.result ); } ); } ); } function loadAllData() { return new Promise( ( resolve, reject ) => { if ( !db ) { reject( 'DB not opened' ); } const transaction = db.transaction( objectStoreName, 'readonly' ); const requestAll = transaction .objectStore( objectStoreName ) .getAll(); requestAll.addEventListener( 'success', () => { resolve( requestAll.result ); } ); } ); } /** * Save data for a specific page and section * * @ignore * @param {string} pageName The current page name (with underscores) * @param {string|null} section The section ID, or null if the whole page is being edited * @param {Object} pageData The page data to save * @return {jQuery.Promise} Promise which resolves on success, or rejects with an error message. */ function saveData( pageName, section, pageData ) { return new Promise( ( resolve, reject ) => { if ( !db ) { reject( 'DB not opened' ); } // Add indexed fields. pageData.pageName = pageName; pageData.section = section || ''; pageData.expiry = getExpiryDate( editRecoveryExpiry ); const transaction = db.transaction( objectStoreName, 'readwrite' ); const objectStore = transaction.objectStore( objectStoreName ); const request = objectStore.put( pageData ); request.addEventListener( 'success', ( event ) => { resolve( event ); } ); request.addEventListener( 'error', ( event ) => { reject( 'Error saving data: ' + event.target.errorCode ); } ); } ); } /** * Delete data relating to a specific page * * @ignore * @param {string} pageName The current page name (with underscores) * @param {string|null} section The section ID, or null if the whole page is being edited * @return {jQuery.Promise} Promise which resolves on success, or rejects with an error message. */ function deleteData( pageName, section ) { return new Promise( ( resolve, reject ) => { if ( !db ) { reject( 'DB not opened' ); } const transaction = db.transaction( objectStoreName, 'readwrite' ); const objectStore = transaction.objectStore( objectStoreName ); const request = objectStore.delete( [ pageName, section || '' ] ); request.addEventListener( 'success', resolve ); request.addEventListener( 'error', () => { reject( 'Error opening cursor' ); } ); } ); } /** * Returns the date diff seconds in the future * * @ignore * @param {number} diff Seconds in the future * @return {number} Timestamp of diff days in the future */ function getExpiryDate( diff ) { return ( Date.now() / 1000 ) + diff; } /** * Delete expired data * * @ignore * @return {jQuery.Promise} Promise which resolves on success, or rejects with an error message. */ function deleteExpiredData() { return new Promise( ( resolve, reject ) => { if ( !db ) { reject( 'DB not opened' ); } const transaction = db.transaction( objectStoreName, 'readwrite' ); const objectStore = transaction.objectStore( objectStoreName ); const expiry = objectStore.index( 'expiry' ); const now = Date.now() / 1000; const expired = expiry.getAll( IDBKeyRange.upperBound( now, true ) ); expired.addEventListener( 'success', ( event ) => { const cursors = event.target.result; if ( cursors.length > 0 ) { const deletions = []; cursors.forEach( ( cursor ) => { deletions.push( deleteData( cursor.pageName, cursor.section ) ); } ); Promise.all( deletions ).then( resolve ); } else { resolve(); } } ); expired.addEventListener( 'error', () => { reject( 'Error getting filtered data' ); } ); } ); } /** * Close database * * @ignore */ function closeDatabase() { if ( db ) { db.close(); } } module.exports = { openDatabase: openDatabaseLocal, closeDatabase, loadData, loadAllData, saveData, deleteData, deleteExpiredData }; PK ! �>�j� � postEdit.jsnu �Iw�� 'use strict'; mw.hook( 'postEdit' ).add( () => { // Only continue to delete the data if the data-saved flag hasn't been set in ./edit.js if ( !mw.storage.session.get( 'EditRecovery-data-saved' ) ) { return; } const storage = require( './storage.js' ); storage.openDatabase().then( () => { const pageName = mw.config.get( 'wgPageName' ); const section = mw.storage.session.get( pageName + '-editRecoverySection' ) || null; // Delete the sessionStorage items mw.storage.session.remove( pageName + '-editRecoverySection' ); mw.storage.session.remove( 'EditRecovery-data-saved' ); storage.deleteData( pageName, section ); storage.closeDatabase(); } ); } ); PK ! V0Z2� � LoadNotification.jsnu �Iw�� PK ! M�^�x x 5 styles.lessnu �Iw�� PK ! �LȌ�* �* � edit.jsnu �Iw�� PK ! ���v� � �5 storage.jsnu �Iw�� PK ! �>�j� � �O postEdit.jsnu �Iw�� PK z �R
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка