From fb2f0d003c93dfab9256048491decdb89df2b61a Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Thu, 9 Nov 2023 14:45:31 +0800 Subject: [PATCH] Edit Recovery: add new special page to list unsaved changes Add Special:EditRecovery to dispaly a basic list of all pages with locally-saved edit recovery data. This change just sets up the initial work for this, and the actual design and improved UX will come in subsequent changes. Bug: T347673 Change-Id: I8edbfd21258fcb2e4fc9f3e4ded9876d6635d752 --- autoload.php | 1 + includes/specialpage/SpecialPageFactory.php | 8 +++ includes/specials/SpecialEditRecovery.php | 41 +++++++++++++++ languages/i18n/en.json | 4 ++ languages/i18n/qqq.json | 4 ++ languages/messages/MessagesEn.php | 1 + resources/Resources.php | 19 +++++++ .../src/mediawiki.editRecovery/storage.js | 16 ++++++ .../SpecialEditRecovery.vue | 50 +++++++++++++++++++ .../mediawiki.special.editrecovery/init.js | 10 ++++ .../styles.less | 3 ++ 11 files changed, 157 insertions(+) create mode 100644 includes/specials/SpecialEditRecovery.php create mode 100644 resources/src/mediawiki.special.editrecovery/SpecialEditRecovery.vue create mode 100644 resources/src/mediawiki.special.editrecovery/init.js create mode 100644 resources/src/mediawiki.special.editrecovery/styles.less diff --git a/autoload.php b/autoload.php index dd99f81f26e..a5e4918d52a 100644 --- a/autoload.php +++ b/autoload.php @@ -2053,6 +2053,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Specials\\SpecialDiff' => __DIR__ . '/includes/specials/SpecialDiff.php', 'MediaWiki\\Specials\\SpecialDoubleRedirects' => __DIR__ . '/includes/specials/SpecialDoubleRedirects.php', 'MediaWiki\\Specials\\SpecialEditPage' => __DIR__ . '/includes/specials/SpecialEditPage.php', + 'MediaWiki\\Specials\\SpecialEditRecovery' => __DIR__ . '/includes/specials/SpecialEditRecovery.php', 'MediaWiki\\Specials\\SpecialEditTags' => __DIR__ . '/includes/specials/SpecialEditTags.php', 'MediaWiki\\Specials\\SpecialEditWatchlist' => __DIR__ . '/includes/specials/SpecialEditWatchlist.php', 'MediaWiki\\Specials\\SpecialEmailInvalidate' => __DIR__ . '/includes/specials/SpecialEmailInvalidate.php', diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php index 62f399abeb6..970d9157d66 100644 --- a/includes/specialpage/SpecialPageFactory.php +++ b/includes/specialpage/SpecialPageFactory.php @@ -70,6 +70,7 @@ use MediaWiki\Specials\SpecialDeletePage; use MediaWiki\Specials\SpecialDiff; use MediaWiki\Specials\SpecialDoubleRedirects; use MediaWiki\Specials\SpecialEditPage; +use MediaWiki\Specials\SpecialEditRecovery; use MediaWiki\Specials\SpecialEditTags; use MediaWiki\Specials\SpecialEditWatchlist; use MediaWiki\Specials\SpecialEmailInvalidate; @@ -1232,6 +1233,7 @@ class SpecialPageFactory { MainConfigNames::EnableEmail, MainConfigNames::EnableJavaScriptTest, MainConfigNames::EnableSpecialMute, + MainConfigNames::EnableEditRecovery, MainConfigNames::PageLanguageUseDB, MainConfigNames::SpecialPages, ]; @@ -1356,6 +1358,12 @@ class SpecialPageFactory { ]; } + if ( $this->options->get( MainConfigNames::EnableEditRecovery ) ) { + $this->list['EditRecovery'] = [ + 'class' => SpecialEditRecovery::class, + ]; + } + // Add extension special pages $this->list = array_merge( $this->list, $this->options->get( MainConfigNames::SpecialPages ) ); diff --git a/includes/specials/SpecialEditRecovery.php b/includes/specials/SpecialEditRecovery.php new file mode 100644 index 00000000000..32e724f1652 --- /dev/null +++ b/includes/specials/SpecialEditRecovery.php @@ -0,0 +1,41 @@ +addHelpLink( 'Help:Edit_Recovery' ); + $this->getOutput()->addModuleStyles( 'mediawiki.special.editrecovery.styles' ); + $this->getOutput()->addModules( 'mediawiki.special.editrecovery' ); + $noJs = Html::element( + 'span', + [ 'class' => 'error mw-EditRecovery-special-nojs-notice' ], + $this->msg( 'edit-recovery-nojs-placeholder' ) + ); + $placeholder = Html::rawElement( 'div', [ 'class' => 'mw-EditRecovery-special' ], $noJs ); + $this->getOutput()->addHTML( $placeholder ); + } +} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 6c30e17c38d..f1713bc55be 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -650,6 +650,10 @@ "resettokens-done": "Tokens reset.", "resettokens-resetbutton": "Reset selected tokens", "sig-text": "--$1", + "editrecovery": "Edit Recovery", + "edit-recovery-nojs-placeholder": "JavaScript is required for the Edit Recovery feature.", + "edit-recovery-special-intro": "You have unsaved changes to the following {{PLURAL:$1|page or section|pages and/or sections}}:", + "edit-recovery-special-intro-empty": "You have no unsaved changes.", "edit-recovery-loaded-title": "Changes recovered", "edit-recovery-loaded-message": "Your unsaved changes have been automatically recovered.", "edit-recovery-loaded-show": "Show changes", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 6d7516ef569..0cd4884285b 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -904,6 +904,10 @@ "resettokens-done": "Message shown on [[Special:ResetTokens]] after the tokens have been reset successfully.", "resettokens-resetbutton": "Form submit button on [[Special:ResetTokens]].", "sig-text": "{{notranslate}} This is the text that appears when you click on the signature button (second button from the right) on the edit toolbar. $1 will be replaced with four tildes (which cannot be included directly in the message for technical reasons).", + "editrecovery": "{{doc-special|EditRecovery}}", + "edit-recovery-nojs-placeholder": "Error message shown when JavaScript is disabled.", + "edit-recovery-special-intro": "Message shown above a list of linked page titles.\n\nParameters:\n\n* $1 — Integer number of list items.", + "edit-recovery-special-intro-empty": "Message shown instead of the list of pages when the list is empty.", "edit-recovery-loaded-title": "Title for a notification toast popup shown when an in-progress edit is recovered.", "edit-recovery-loaded-message": "Message shown in a notification toast popup when an in-progress edit is recovered.", "edit-recovery-loaded-show": "Button text in a notification toast popup shown when an in-progress edit is recovered, used to show changes.", diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index ddf1c0a98e9..a982a874e70 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -430,6 +430,7 @@ $specialPageAliases = [ 'Diff' => [ 'Diff' ], 'DoubleRedirects' => [ 'DoubleRedirects' ], 'EditPage' => [ 'EditPage', 'Edit' ], + 'EditRecovery' => [ 'EditRecovery' ], 'EditTags' => [ 'EditTags' ], 'EditWatchlist' => [ 'EditWatchlist' ], 'Emailuser' => [ 'EmailUser', 'Email' ], diff --git a/resources/Resources.php b/resources/Resources.php index 90a31a26ddb..a751691f35f 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2289,6 +2289,25 @@ return [ 'mediawiki.special.preferences.styles.ooui' => [ 'styles' => 'resources/src/mediawiki.special.preferences.styles.ooui.less', ], + 'mediawiki.special.editrecovery.styles' => [ + 'styles' => 'resources/src/mediawiki.special.editrecovery/styles.less', + ], + 'mediawiki.special.editrecovery' => [ + 'packageFiles' => [ + 'resources/src/mediawiki.special.editrecovery/init.js', + 'resources/src/mediawiki.special.editrecovery/SpecialEditRecovery.vue', + 'resources/src/mediawiki.editRecovery/storage.js', + ], + 'dependencies' => [ + 'vue', + ], + 'messages' => [ + 'editlink', + 'parentheses', + 'edit-recovery-special-intro', + 'edit-recovery-special-intro-empty', + ], + ], 'mediawiki.special.search' => [ 'scripts' => 'resources/src/mediawiki.special.search/search.js', 'dependencies' => 'mediawiki.widgets.SearchInputWidget', diff --git a/resources/src/mediawiki.editRecovery/storage.js b/resources/src/mediawiki.editRecovery/storage.js index 5b211364418..19ecb3c9882 100644 --- a/resources/src/mediawiki.editRecovery/storage.js +++ b/resources/src/mediawiki.editRecovery/storage.js @@ -85,6 +85,21 @@ function loadData( pageName, section ) { } ); } +function loadAllData() { + return new Promise( function ( resolve, reject ) { + if ( !db ) { + reject( 'DB not opened' ); + } + const transaction = db.transaction( objectStoreName, 'readonly' ); + const requestAll = transaction + .objectStore( objectStoreName ) + .getAll(); + requestAll.addEventListener( 'success', function () { + resolve( requestAll.result ); + } ); + } ); +} + /** * Save data for a specific page and section * @@ -213,6 +228,7 @@ module.exports = { openDatabase: openDatabaseLocal, closeDatabase: closeDatabase, loadData: loadData, + loadAllData: loadAllData, saveData: saveData, deleteData: deleteData, deleteExpiredData: deleteExpiredData diff --git a/resources/src/mediawiki.special.editrecovery/SpecialEditRecovery.vue b/resources/src/mediawiki.special.editrecovery/SpecialEditRecovery.vue new file mode 100644 index 00000000000..9fe619564b4 --- /dev/null +++ b/resources/src/mediawiki.special.editrecovery/SpecialEditRecovery.vue @@ -0,0 +1,50 @@ + + + diff --git a/resources/src/mediawiki.special.editrecovery/init.js b/resources/src/mediawiki.special.editrecovery/init.js new file mode 100644 index 00000000000..82dc5492e54 --- /dev/null +++ b/resources/src/mediawiki.special.editrecovery/init.js @@ -0,0 +1,10 @@ +( function () { + 'use strict'; + const outer = document.querySelector( '.mw-EditRecovery-special' ); + if ( !outer ) { + return; + } + const Vue = require( 'vue' ); + const App = require( './SpecialEditRecovery.vue' ); + Vue.createMwApp( App ).mount( outer ); +}() ); diff --git a/resources/src/mediawiki.special.editrecovery/styles.less b/resources/src/mediawiki.special.editrecovery/styles.less new file mode 100644 index 00000000000..6f1833099be --- /dev/null +++ b/resources/src/mediawiki.special.editrecovery/styles.less @@ -0,0 +1,3 @@ +.client-js .mw-EditRecovery-special-nojs-notice { + display: none; +}