TheDJ has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/159626

Change subject: Drafts: this time using indexeddb
......................................................................

Drafts: this time using indexeddb

Advantadges:
- doesn't fill up storage
- fully async

Disadvantadge:
- requires WebSQL based shim in order to support Safari and older IE.

Other changes
- Does 30 day expiry
- No longer a max # of drafts
- Seperate module
- Added a preference for this

Could also easily add a Special:Drafts page to manage these.
Wondering if this thing should be renamed from drafts to 'backups' or
something.

Change-Id: I0ea580e4acc6ed539f4314ab3f5378f95a451c1c
---
M includes/DefaultSettings.php
M includes/EditPage.php
M includes/Preferences.php
M languages/i18n/en.json
M resources/Resources.php
A resources/src/mediawiki.action/mediawiki.action.edit.drafts.js
M resources/src/mediawiki.action/mediawiki.action.edit.js
7 files changed, 192 insertions(+), 195 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/26/159626/1

diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 26ce83c..1ce75fd 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -4283,6 +4283,7 @@
        'wllimit' => 250,
        'useeditwarning' => 1,
        'prefershttps' => 1,
+       'localdrafts' => 1,
 );
 
 /**
diff --git a/includes/EditPage.php b/includes/EditPage.php
index a14191a..8c55dcb 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -2066,6 +2066,10 @@
                        $wgOut->addModules( 'mediawiki.action.edit.editWarning' 
);
                }
 
+               if ( $wgUser->getOption( 'localdrafts', false ) ) {
+                       $wgOut->addModules( 'mediawiki.action.edit.drafts' );
+               }
+
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
 
                # Enabled article-related sidebar, toplinks, etc.
diff --git a/includes/Preferences.php b/includes/Preferences.php
index eb29e41..20adb40 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -834,6 +834,11 @@
                        'section' => 'editing/editor',
                        'label-message' => 'tog-useeditwarning',
                );
+               $defaultPreferences['localdrafts'] = array(
+                       'type' => 'toggle',
+                       'section' => 'editing/editor',
+                       'label-message' => 'tog-localdrafts',
+               );
                $defaultPreferences['showtoolbar'] = array(
                        'type' => 'toggle',
                        'section' => 'editing/editor',
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index 196b491..9ced786 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -42,6 +42,7 @@
        "tog-norollbackdiff": "Omit diff after performing a rollback",
        "tog-useeditwarning": "Warn me when I leave an edit page with unsaved 
changes",
        "tog-prefershttps": "Always use a secure connection when logged in",
+       "tog-localdrafts": "Back-up unsaved changes using drafts on your 
computer",
        "underline-always": "Always",
        "underline-never": "Never",
        "underline-default": "Skin or browser default",
diff --git a/resources/Resources.php b/resources/Resources.php
index 7a3a011..b566216 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -991,19 +991,25 @@
                        'mediawiki.action.edit.styles',
                        'jquery.textSelection',
                        'jquery.byteLimit',
-                       'mediawiki.notification',
-                       'jquery.throttle-debounce',
                ),
                'position' => 'top',
+       ),
+       'mediawiki.action.edit.styles' => array(
+               'styles' => 
'resources/src/mediawiki.action/mediawiki.action.edit.styles.css',
+               'position' => 'top',
+       ),
+       'mediawiki.action.edit.drafts' => array(
+               'scripts' => 
'resources/src/mediawiki.action/mediawiki.action.edit.drafts.js',
+               'dependencies' => array(
+                       'mediawiki.notification',
+                       'jquery.throttle-debounce',
+                       'jquery.indexeddb',
+               ),
                'messages' => array(
                        'textarea-draft-found',
                        'textarea-use-draft',
                        'textarea-use-current-version',
                ),
-       ),
-       'mediawiki.action.edit.styles' => array(
-               'styles' => 
'resources/src/mediawiki.action/mediawiki.action.edit.styles.css',
-               'position' => 'top',
        ),
        'mediawiki.action.edit.collapsibleFooter' => array(
                'scripts' => 
'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js',
diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.drafts.js 
b/resources/src/mediawiki.action/mediawiki.action.edit.drafts.js
new file mode 100644
index 0000000..a716882
--- /dev/null
+++ b/resources/src/mediawiki.action/mediawiki.action.edit.drafts.js
@@ -0,0 +1,168 @@
+/**
+ * Interface for the classic edit toolbar.
+ *
+ * @class mw.toolbar
+ * @singleton
+ */
+( function ( mw, $ ) {
+       var     dbPromise,
+               indexedDB = window.indexedDB || window.mozIndexedDB || 
window.webkitIndexedDB || window.
+msIndexedDB,
+               conf = mw.config.get( [
+                       'wgCookiePrefix',
+                       'wgAction',
+                       'wgPageName'
+               ] ),
+               draftsDBName = conf.wgCookiePrefix + "-mediawiki-drafts",
+               draftsTable = "drafts";
+
+       /**
+        * Open the database, do an update of the schema if required
+        */
+       function openDraftsDatabase() {
+               dbOpenPromise = $.indexedDB( draftsDBName, {
+                       "schema" : {
+                               "1" : function( transaction ) {
+                                       var store = 
transaction.createObjectStore( draftsTable, {
+                                               keyPath: 'wgPageName'
+                                       } );
+                                       store.createIndex( 'lastUpdated' );
+                               }
+                       }
+               } ).done( function(db) {
+                       mw.log( 'Database ' + draftsDBName + ' opened or 
created' );
+                       // Check if there is a draft for the current page and 
show window
+               } ).fail( function( db ) {
+                       mw.log.warn( 'Database could not be opened' );
+               } );
+               return dbOpenPromise;
+       }
+
+       function purgeDb() {
+               var parsed, starttime, threshold = new Date();
+
+               // We purge stuff after a draft has not been used in a month
+               threshold = threshold.getTime() - ( 30 * 24 * 60 * 60 * 1000 );
+
+               return $.indexedDB( draftsDBName  )
+                       .transaction( draftsTable )
+                               .progress( function ( trans ) {
+                                       trans.objectStore( draftsTable ).index( 
'lastUpdated' ).each( function ( item ) {
+                                                       parsed = new Date( )
+                                                       parsed.setTime( 
item.value.lastUpdated );
+                                                       if ( threshold > parsed 
) {
+                                                               item.delete();
+                                                       }
+                                               } )
+                               } )
+                               .fail( function(error) {
+                                       mw.log.warn( 'something went wrong in 
the purging: ' + error);
+                               });
+       }
+
+       function saveToDb( draftEntry ) {
+               return $.indexedDB( draftsDBName  )
+                       .transaction( draftsTable )
+                               .progress( function ( trans ) {
+                                       trans.objectStore( draftsTable )
+                                               .get( draftEntry.wgPageName )
+                                               .done( function ( result /*, 
event*/ ) {
+                                                       draftEntry.lastUpdated 
= (new Date()).getTime();
+
+                                                       if ( !result ) {
+                                                               mw.log( 
'successfully saved: ' + draftEntry.wgPageName );
+                                                               
trans.objectStore( draftsTable ).add( draftEntry )
+                                                               // TODO Check 
max size
+                                                       } else {
+                                                               mw.log( 
'successfully updated: ' + draftEntry.wgPageName );
+                                                               
trans.objectStore( draftsTable ).put( draftEntry )
+                                                       }
+                                               } )
+                               });
+       }
+
+       function getFromDb( pageName ) {
+               return $.indexedDB( draftsDBName  )
+                                       .objectStore(draftsTable)
+                                       .get( wgPageName );
+       }
+
+       function removeFromDb( pageName ) {
+               return $.indexedDB( draftsDBName  )
+                                       .objectStore(draftsTable)
+                                       .delete( wgPageName );
+       }
+
+       function textAreaAutoSaveInit() {
+               var pageName = conf.wgPageName,
+                       $textArea = $( '#wpTextbox1' ),
+                       $editForm = $( '#editform' ),
+                       $wpStarttime = $editForm.find( '[name="wpStarttime"]' ),
+                       $wpEdittime = $editForm.find( '[name="wpEdittime"]' ),
+                       $wpSection = $editForm.find( '[name="wpSection"]' );
+
+               $textArea.on( 'input', $.debounce( 300, function () {
+                       var draftEntry = {
+                               wgPageName: pageName,
+                               wpStarttime: $wpStarttime.val(),
+                               wpEdittime: $wpEdittime.val(),
+                               wpSection: $wpSection.val(),
+                               content: $textArea.val()
+                       };
+                       $.when( dbPromise, saveToDb( draftEntry ) )
+                               .fail( function ( error /*, errorEvent*/ ) {
+                                       mw.log.warn( 'could not save entry: ' + 
draftEntry.wgPageName + ' ' + error );
+                               });
+               } ) );
+
+               $( '#wpSave, #mw-editform-cancel' ).click( function () {
+                       $.when( dbPromise, removeFromDb( pageName ) )
+                               .done( function () {
+                                       mw.log('removed from DB: ' + pageName );
+                               } );
+               } );
+
+               $.when( dbPromise ).done( function () {
+                       getFromDb( pageName ).done( function ( currentDraft /*, 
event */ ) {
+                               var node, notif, $replaceLink, $discardLink;
+                               if ( currentDraft && $.trim( currentDraft ) !== 
$.trim( $textArea.val() ) ) {
+                                       $replaceLink = $( '<a>' )
+                                               .text( mw.msg( 
'textarea-use-draft' ) )
+                                               .click( function ( e ) {
+                                                       $textArea.val( 
currentDraft.content );
+                                                       $wpStarttime.val( 
currentDraft.wpStarttime );
+                                                       $wpEdittime.val( 
currentDraft.wpEdittime );
+                                                       $wpSection.val( 
currentDraft.wpSection );
+                                                       e.preventDefault();
+                                                       notif.close();
+                                               } );
+
+                                       $discardLink = $( '<a>' )
+                                               .text( mw.msg( 
'textarea-use-current-version' ) )
+                                               .click( function ( e ) {
+                                                       $.when( dbPromise, 
removeFromDb( pageName ) );
+                                                       e.preventDefault();
+                                                       notif.close();
+                                               } );
+
+                                       node = $( '<span>' ).append(
+                                               $( '<span>' ).text( mw.msg( 
'textarea-draft-found' ) ),
+                                               $( '<br>' ),
+                                               $replaceLink,
+                                               document.createTextNode( ' · ' 
),
+                                               $discardLink
+                                       );
+                                       notif = mw.notification.notify( node, { 
autoHide: false } );
+                               }
+                       } );
+               } );
+       }
+
+       // TODO can be moved before document ready..
+       if ( indexedDB && ( conf.wgAction === 'edit' || conf.wgAction === 
'submit' ) ) {
+               dbPromise = openDraftsDatabase();
+               $.when( dbPromise, purgeDb() );
+               $.when( dbPromise, $.ready ).done( textAreaAutoSaveInit );
+       }
+
+}( mediaWiki, jQuery ) );
\ No newline at end of file
diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.js 
b/resources/src/mediawiki.action/mediawiki.action.edit.js
index 36b5cee..43f39f1 100644
--- a/resources/src/mediawiki.action/mediawiki.action.edit.js
+++ b/resources/src/mediawiki.action/mediawiki.action.edit.js
@@ -5,7 +5,7 @@
  * @singleton
  */
 ( function ( mw, $ ) {
-       var toolbar, isReady, $toolbar, queue, slice, $currentFocused, 
draftsIdx,
+       var toolbar, isReady, $toolbar, queue, slice, $currentFocused,
                conf = mw.config.get( [
                        'wgCookiePrefix',
                        'wgAction',
@@ -155,193 +155,6 @@
        // Expose API publicly
        mw.toolbar = toolbar;
 
-       function saveToLocalStorage( draftEntry ) {
-               var oldestPage,
-                       d,
-                       size = 0,
-                       draftsLimit = 10,
-                       newEntry = {
-                               'a': draftEntry.wpStarttime, // Age, updated 
for each edit session
-                               't': draftEntry.wpStarttime,
-                               'e': draftEntry.wpEdittime,
-                               's': draftEntry.wpSection
-                       };
-               if ( !( draftEntry.pageName in draftsIdx ) ) {
-                       for ( d in draftsIdx ) {
-                               if ( draftsIdx.hasOwnProperty( d ) ) {
-                                       size++;
-                               }
-                       }
-                       if ( size === draftsLimit ) {
-                               oldestPage = oldestDraft();
-                               mw.log( 'Maximum of ' + draftsLimit + ' drafts 
reached. Removing a draft for ' + oldestPage );
-                               removeFromLocalStorage( oldestPage, false );
-                       }
-                       mw.log( 'Adding a draft for ' + draftEntry.pageName );
-                       draftsIdx[draftEntry.pageName] = newEntry;
-               }
-               localStorage.setItem( conf.wgCookiePrefix + '-drafts' + 
draftEntry.pageName, draftEntry.content );
-               writeDraftsIndex();
-       }
-
-       function oldestDraft() {
-               var pageName,
-                       draftEntry,
-                       oldestPage;
-               for ( pageName in draftsIdx ) {
-                       draftEntry = draftsIdx[pageName];
-                       if ( oldestPage === undefined || draftEntry.a < 
draftsIdx[oldestPage].a ) {
-                               oldestPage = pageName;
-                       }
-               }
-               return oldestPage;
-       }
-
-       function getLocalStorage( pageName ) {
-               var draftEntry = draftsIdx[pageName];
-               if ( draftEntry ) {
-                       return {
-                               wpStarttime: draftEntry.t,
-                               wpEdittime: draftEntry.e,
-                               wpSection: draftEntry.s,
-                               content: localStorage.getItem( 
conf.wgCookiePrefix + '-drafts' + pageName )
-                       };
-               }
-               return null;
-       }
-
-       /**
-        * @private
-        * @param {string} pageName
-        * @param {boolean} [updateIndex=true]
-        */
-       function removeFromLocalStorage( pageName, updateIndex ) {
-               delete draftsIdx[pageName];
-               localStorage.removeItem( conf.wgCookiePrefix + '-drafts' + 
pageName );
-               if ( updateIndex === undefined || updateIndex === true ) {
-                       writeDraftsIndex();
-               }
-       }
-
-       function readDraftsIndex() {
-               try {
-                       draftsIdx = JSON.parse( localStorage.getItem( 
conf.wgCookiePrefix + '-draftsIdx' ) );
-               } catch ( e ) {
-                       mw.log.error( e );
-               }
-               if ( !draftsIdx ) {
-                       draftsIdx = {};
-               }
-       }
-
-       function writeDraftsIndex() {
-               localStorage.setItem( conf.wgCookiePrefix + '-draftsIdx', 
JSON.stringify( draftsIdx ) );
-       }
-
-       function purgeOldDrafts() {
-               var draftEntry, pageName, i,
-                       parsed,
-                       starttime,
-                       toDelete = [],
-                       threshold = new Date();
-
-               // We purge stuff after a draft has not been used in a month
-               threshold.setTime( threshold.getTime() - ( 30 * 24 * 60 * 60 * 
1000 ) );
-               for ( pageName in draftsIdx ) {
-                       draftEntry = draftsIdx[pageName];
-                       parsed = draftEntry.a.match( 
/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ );
-                       starttime = new Date( Date.UTC( parsed[1], parsed[2] - 
1, parsed[3], parsed[4], parsed[5], parsed[6] ) );
-                       if ( threshold > starttime ) {
-                               toDelete.push( pageName );
-                       }
-               }
-               if ( toDelete.length ) {
-                       for ( i = 0; i < toDelete.length; i++ ) {
-                               pageName = toDelete[i];
-                               mw.log.warn( 'Removing draft older than 30 days 
for ' + pageName );
-                               removeFromLocalStorage( pageName, false );
-                       }
-                       writeDraftsIndex();
-               }
-       }
-
-       function textAreaAutoSaveInit() {
-               // If there's no localStorage, there's no point in doing the 
checks or adding listeners.
-               if ( 'localStorage' in window && localStorage !== null &&
-                       ( conf.wgAction === 'edit' || conf.wgAction === 
'submit' ) ) {
-                       var pageName = conf.wgPageName,
-                               currentDraft,
-                               notif,
-                               $replaceLink,
-                               $discardLink,
-                               $textArea = $( '#wpTextbox1' ),
-                               $editForm = $( '#editform' ),
-                               $wpStarttime = $editForm.find( 
'[name="wpStarttime"]' ),
-                               $wpEdittime = $editForm.find( 
'[name="wpEdittime"]' ),
-                               $wpSection = $editForm.find( 
'[name="wpSection"]' ),
-                               node = $( '<span>' );
-
-                       readDraftsIndex();
-                       purgeOldDrafts();
-
-                       $textArea.on( 'input', $.debounce( 300, function () {
-                               var draftEntry = {
-                                       pageName: pageName,
-                                       wpStarttime: $wpStarttime.val(),
-                                       wpEdittime: $wpEdittime.val(),
-                                       wpSection: $wpSection.val(),
-                                       content: $textArea.val()
-                               };
-                               try {
-                                       saveToLocalStorage( draftEntry );
-                               } catch ( e ) {
-                                       // Assume any error is due to the quota 
being exceeded,
-                                       // per 
http://chrisberkhout.com/blog/localstorage-errors/
-                                       mw.log.warn( 'Unable to save draft. 
localStorage is full.', e );
-                                       removeFromLocalStorage( pageName );
-                               }
-                       } ) );
-
-                       $( '#wpSave, #mw-editform-cancel' ).click( function () {
-                               removeFromLocalStorage( pageName );
-                       } );
-
-                       currentDraft = getLocalStorage( pageName );
-                       if ( currentDraft && $.trim( currentDraft ) !== $.trim( 
$textArea.val() ) ) {
-                               $replaceLink = $( '<a>' )
-                                       .text( mw.msg( 'textarea-use-draft' ) )
-                                       .click( function ( e ) {
-                                               var draftEntry = 
getLocalStorage( pageName );
-                                               draftsIdx[pageName].a = 
$wpStarttime.val();
-                                               writeDraftsIndex();
-                                               $textArea.val( 
draftEntry.content );
-                                               $wpStarttime.val( 
draftEntry.wpStarttime );
-                                               $wpEdittime.val( 
draftEntry.wpEdittime );
-                                               $wpSection.val( 
draftEntry.wpSection );
-                                               e.preventDefault();
-                                               notif.close();
-                                       } );
-
-                               $discardLink = $( '<a>' )
-                                       .text( mw.msg( 
'textarea-use-current-version' ) )
-                                       .click( function ( e ) {
-                                               removeFromLocalStorage( 
pageName );
-                                               e.preventDefault();
-                                               notif.close();
-                                       } );
-
-                               node.append(
-                                       $( '<span>' ).text( mw.msg( 
'textarea-draft-found' ) ),
-                                       $( '<br>' ),
-                                       $replaceLink,
-                                       document.createTextNode( ' · ' ),
-                                       $discardLink
-                               );
-                               notif = mw.notification.notify( node, { 
autoHide: false } );
-                       }
-               }
-       }
-
        $( function () {
                var i, b, editBox, scrollTop, $editForm;
 
@@ -393,7 +206,6 @@
                        $currentFocused = $( this );
                } );
 
-               textAreaAutoSaveInit();
        } );
 
 }( mediaWiki, jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/159626
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I0ea580e4acc6ed539f4314ab3f5378f95a451c1c
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: TheDJ <hartman.w...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to