User:PerfektesChaos/js/autoBackup/d.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/// User:PerfektesChaos/js/autoBackup/d.js
/// 2021-05-21 PerfektesChaos@de.wikipedia
// Backup Wiki TEXTAREA in regular intervals
// ResourceLoader:  compatible;
//    dependencies: user,
//                  mediawiki.api, mediawiki.user, mediawiki.util
/// Fingerprint: #0#0#
/// @license: CC-by-sa/4.0
/// <nowiki>
/* jshint forin:false                                                  */
/* global  window:false, JSON:false                                    */
/* jshint bitwise:true, curly:true, eqeqeq:true, latedef:true,
         laxbreak:true,
         nocomma:true, strict:true, undef:true, unused:true            */



( function ( mw, $ ) {
   "use strict";
   var VERSION = -2.4,
       BAK     = "autoBackup";
   if ( typeof mw.libs[ BAK ]  !==  "object"
        ||   ! mw.libs[ BAK ] ) {
      mw.libs[ BAK ] = { };
   }
   mw.libs[ BAK ].type  =  BAK;
   BAK       = mw.libs[ BAK ];
   BAK.doc   =  "[[w:en:User:PerfektesChaos/js/" + BAK + "]]";
   BAK.vsn   =  VERSION;
   BAK.cnf   =  { maxAge:    72,
                  maxHist:    5,
                  maxPages:  10,
                  maxRev:     3,
                  mid:        5,
                  msec:     300000 };
   BAK.disk  =  { self:  "AutoBackupPerfectChaos",
                  stick: "newest|subject" };
   BAK.gui   =  { };
   BAK.util  =  { };
   if ( ! typeof BAK.opt  ||  typeof BAK.opt  !==  "object" ) {
      BAK.opt  =  { };
   }

   // User options:
   //   .maxAge     number of full hours to remember
   //   .maxHist    number of history entries per revision to keep
   //   .maxPages   number of pages to handle simultaneously
   //   .maxRev     number of revisions to keep
   //   .mid        number of minutes for scheduled snapshots
   //   .portlet    add portlet link



   /*
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    * published by the Free Software Foundation; either version 2 of the
    * License, or (at your option) any later version.
    *
    * This program is distributed in the hope that it will be useful,
    * but WITHOUT ANY WARRANTY; without even the implied warranty of
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    * GNU General Public License for more details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program;
    * if not, write to the Free Software Foundation, Inc.,
    * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    * http://www.gnu.org/copyleft/gpl.html
    */



   // Requires: JavaScript  1.3   (String.charCodeAt String.replace)
   //           ECMA        262-3 § 11.8.5  (string comparison operators)
   //           MediaWiki   1.18 (mw.libs, jQuery core)



/*
   .pages
      wgArticleId   { }
         newest: timestamp
         subject: last known page name
         wgCurRevisionId   { }
            newest:  timestamp
            history: [
                       [ timestamp-new,   snapshot ],
                       [ timestamp-old,   snapshot ],
                       [ timestamp-older, snapshot ],
                     ]
 */



   BAK.cnf.text  =  {
      // 2014-09-23 PerfektesChaos@de.wikipedia
      "BAKself":       {"en": "AutoBackup",
                        "de": "AutoBackup"},
      "IntJSONparse":  {"en": "Internal ERROR -- JSON.parse",
                        "de": "Interner FEHLER -- JSON.parse"},
      "NoLSavail":     {"en": "No localStorage available",
                        "de": "Kein localStorage zugreifbar"},
      "otherPages":    {"en": "Other Pages",
                        "de": "Andere Seiten"},
      "pending":       {"en": "Recover from pending abort?",
                        "de": "Wiederherstellung nach Abbruch?"},
      "setItemExcept": {"en": "ERROR setting localStorage",
                        "de": "FEHLER beim Setzen im localStorage"},
      "thisPage":      {"en": "this page",       //oldid
                        "de": "diese Seite"},
      "VanishedLS":    {"en": "ERROR: localStorage vanished",
                        "de": "FEHLER: localStorage verschwunden"},
      "WriteCrash":    {"en": "Crash on localStorage write attempt",
                        "de": "Schreibversuch auf localStorage versagt"}
   };   // .cnf.text



   BAK.cnf.translang  =  {
      // 2012-11-30 PerfektesChaos@de.wikipedia
      "de" :        "de",
      "de-at" :     "de",
      "de-ch" :     "de",
      "de-formal" : "de",
      "als" :       "de",
      "bar" :       "de",
      "dsb" :       "de",
      "frr" :       "de",
      "gsw" :       "de",
      "hsb" :       "de",
      "ksh" :       "de",
      "lb" :        "de",
      "nds" :       "de",
      "pdc" :       "de",
      "pdt" :       "de",
      "pfl" :       "de",
      "sli" :       "de",
      "stq" :       "de",
      "vmf" :       "de"
   };   // .cnf.translang



   BAK.cnf.favorite  =  function () {
      // Guess user language
      // Uses:
      //    this
      //    >  .translang
      //    >< .slang
      //    mw.config.get()
      // 2012-11-21 PerfektesChaos@de.wikipedia
      var s;
      if ( ! this.slang ) {
         s  =  mw.config.get( "wgUserLanguage" ).toLowerCase();
         s  =  this.translang[ s ];
         if ( s ) {
            this.slang  =  s;
         } else {
            this.slang  =  "en";
         }
      }
   };   // .cnf.favorite()



   BAK.cnf.feature  =  function ( apply ) {
      // Wrap message text access for user language
      // Precondition:
      //    apply  -- text keyword
      // Postcondition:
      //    Return text closest to user language
      // Uses:
      //    this
      //    >  .cnf.text
      //    .cnf.favorite()
      // Remark: To be replaced
      //         if one day ResourceLoader3 gives access to
      //         gadget@translatewiki
      // 2012-11-04 PerfektesChaos@de.wikipedia
      var e, r;
      if ( ! this.slang ) {
         this.favorite();
      }
      e  =  this.text[ apply ];
      if ( e ) {
         r  =  e[ this.slang ];
         if ( ! r ) {
            r  =  e.en;
            if ( ! r ) {
               r  =  "???" + apply + "???";
            }
         }
      } else {
         r  =  "***" + apply + "***";
      }
      return r;
   };   // .cnf.feature()



   BAK.cnf.fetch  =  function () {
      // Consider user options
      // Uses:
      //    this
      //    >  .opt
      //    >  .learn
      //    >< .cnf.load
      //     < .cnf.maxAge
      //     < .cnf.maxHist
      //     < .cnf.maxPages
      //     < .cnf.maxRev
      //     < .cnf.mid
      //     < .cnf.msec
      //    .gui.facility()
      // 2012-11-30 PerfektesChaos@de.wikipedia
      if ( ! this.load ) {
         if ( typeof BAK.opt === "object" ) {
            if ( typeof BAK.opt.maxAge  ===  "number" ) {
               if ( BAK.opt.maxAge >= 0 ) {
                  this.maxAge  =  Math.floor( BAK.opt.maxAge );
               }
            }
            if ( typeof BAK.opt.maxHist  ===  "number" ) {
               if ( BAK.opt.maxHist >= 1 ) {
                  this.maxHist  =  Math.ceil( BAK.opt.maxHist );
               }
            }
            if ( typeof BAK.opt.maxPages  ===  "number" ) {
               if ( BAK.opt.maxPages >= 1 ) {
                  this.maxPages  =  Math.ceil( BAK.opt.maxPages );
               }
            }
            if ( typeof BAK.opt.maxRev  ===  "number" ) {
               if ( BAK.opt.maxRev >= 1 ) {
                  this.maxRev  =  Math.ceil( BAK.opt.maxRev );
               }
            }
            if ( BAK.learn ) {
               if ( typeof BAK.opt.mid  ===  "number" ) {
                  if ( BAK.opt.mid > 0 ) {
                     this.mid  =  BAK.opt.mid;
                  } else {
                     this.mid  =  false;
                  }
               }
               if ( this.mid ) {
                  this.msec  =  this.mid * 60000;
               } else {
                  this.msec  =  false;
               }
               if ( BAK.opt.portlet ) {
                  BAK.gui.facility();
               }
            }
         }
         this.load  =  true;
      }
   };   // .cnf.fetch()



   BAK.disk.fetch  =  function () {
      // Retrieve pages object from localStorage data
      // Postcondition:
      //    .pages is set to collection object, or false
      // Uses:
      //    this
      //    >  .disk.storage
      //    >< window.localStorage
      //     < .pages
      //     < .disk.lock
      //    .family()
      //    JSON.parse()
      //    .gui.flag()
      // 2014-09-23 PerfektesChaos@de.wikipedia
      var s;
      BAK.pages  =  false;
      if ( typeof window.localStorage  ===  "object" ) {
         BAK.family();
         s  =  window.localStorage.getItem( this.storage );
         if ( s ) {
            try {
               BAK.pages  =  JSON.parse( s );
            } catch ( e ) {
               BAK.gui.flag( "IntJSONparse", false );
               window.localStorage.setItem( this.storage, "" );
            }
         }
      } else {
         BAK.gui.flag( "NoLSavail", false );
         this.lock  =  true;
      }
   };   // .disk.fetch()



   BAK.disk.flush  =  function () {
      // Put .pages object into localStorage
      // Postcondition:
      //    Clear empty .pages object
      // Uses:
      //    this
      //    >  .disk.storage
      //    >< window.localStorage
      //    >< .pages
      //    jQuery.toJSON()
      //    .gui.flag()
      // 2014-09-23 PerfektesChaos@de.wikipedia
      var store  =  false,
          s;
      if ( typeof BAK.pages === "object" ) {
         for ( s in BAK.pages ) {
            store  =  true;
            break;   //  for s
         }   //  for s
      }
      if ( ! store ) {
         this.pages  =  false;
      }
      if ( typeof window.localStorage  ===  "object" ) {
         if ( BAK.pages ) {
            store  =  JSON.stringify( BAK.pages );
            if ( store === "{}") {
               store  =  "";
            }
         } else {
            store  =  "";
         }
         try {
            window.localStorage.setItem( this.storage, store );
            s  =  window.localStorage.getItem( this.storage );
            if ( s !== store ) {
               BAK.gui.flag( "WriteCrash", false );
            }
         } catch ( e ) {
            BAK.gui.flag( "setItemExcept", false );
         }
         window.localStorage.setItem( this.storage, store );
      } else {
         BAK.gui.flag( "VanishedLS", false );
      }
   };   // .disk.flush()



   BAK.gui.face  =  function ( apply ) {
      // Prepare GUI message area for content
      // Precondition:
      //    apply  -- HTML message box content
      // Uses:
      //    this
      //    >  .$page
      //    >< .gui.$top
      //    >< .gui.$div
      //    >< .gui.$msg
      //    mw.util.addCSS()
      //    jQuery()
      //    jQuery().prepend()
      //    jQuery().before()
      //    jQuery().empty()
      //    jQuery().attr()
      //    jQuery().append()
      // 2021-05-21 PerfektesChaos@de.wikipedia
      var s;
      if ( ! this.$div ) {
         mw.util.addCSS( ".cn-fundraiser-banner,"
                         + "#mw-js-message,"
                         + "#siteNotice,"
                         + "#fundraising\n"
                         + "{display: none ! important;}" );
         this.$div  =  $( "<div class='AutoBackupDiv' />" );
         if ( ! this.$top ) {
            this.$top  =  $( "#mw-content-text" );
            if ( this.$top.length ) {
               this.$top.prepend( this.$div );
            } else {
               this.$top  =  $( ".mw-body-content" ).eq( 0 );
               if ( ! this.$top.length ) {
                  this.$top  =  $( "#article" );
                  if ( ! this.$top.length ) {
                     this.$top  =  $( "#content" );
                     if ( ! this.$top.length ) {
                        this.$top  =  BAK.$page;
                     }
                  }
               }
               this.$top.before( this.$div );
            }
         }
      }
      if ( apply ) {
         if ( this.$msg ) {
            this.$msg.empty();
         } else {
            this.$msg  =  $( "<div class='AutoBackupMsg' />" );
            this.$div.prepend( this.$msg );
         }
         if ( this.$msg ) {
            s  =    "display:   block;"
                  + "font-size: 120%;";
            this.$msg.attr( "style", s );
            this.$msg.append( apply );
         }
      } else {
         if ( this.$msg ) {
            this.$msg.attr( "style",  "display: none;" );
         }
      }
   };   // .gui.face()



   BAK.gui.facility  =  function () {
      // Equip portlet with link to trigger
      // Precondition:
      //    document.ready
      // Uses:
      //    >< .cnf.self
      //    >  .type
      //    >  .vsn
      //    .cnf.feature()
      //    mw.util.addPortletLink()
      //    (.fresh)
      // 2017-01-04 PerfektesChaos@de.wikipedia
      var portlet, $portlet;
      if ( ! BAK.cnf.self ) {
         BAK.cnf.self  =  BAK.cnf.feature( "BAKself" );
      }
      portlet   =  mw.util.addPortletLink( "p-cactions",
                                           "#",
                                           BAK.cnf.self,
                                           "ca-" + BAK.type );
      $portlet  =  $( portlet );
      $portlet.click( BAK.fresh );
      $portlet.attr( { title: BAK.type + " " + BAK.vsn } );
   };   // .gui.facility()



   BAK.gui.feed  =  function () {
      // Retrieve content of text area; wpTextbox2 if edit conflict
      // Precondition:
      //    document.ready
      // Postcondition:
      //    Return textarea string; or false
      // Uses:
      //    this
      //    >  .$page
      //    >  .gui.$textarea
      //    >  wikEd
      //    >< .gui.leading
      //     < .gui.$editform
      //     < .gui.$textarea
      //    jQuery.find()
      //    wikEd.UpdateTextarea()
      // 2019-07-01 PerfektesChaos@de.wikipedia
      var r      =  false,
          $form,
          $ta;
      if ( this.leading ) {   // opening
         this.leading    =  false;
         this.$editform  =  false;
         this.$textarea  =  false;
         $form           =  BAK.$page.find( "#editform" );
         if ( $form ) {
            $ta  =  BAK.$page.find( "#wpTextbox2" );
            if ( $ta.length ) {   // edit conflict
               r  =  $ta.val();
            }
            $ta  =  $form.find( "#wpTextbox1" );
            if ( $ta.length ) {   // textarea
               if ( ! $ta.attr( "readonly" ) ) {   // modifiable
                  this.$editform  =  $form;
                  this.$textarea  =  $ta;
               }
            }   // textarea
         }
      }
      if ( this.$textarea  &&  ! r ) {   // editing
         if ( window.wikEd
              &&  typeof window.wikEd  ===  "object"
              &&  ! window.wikEd.disabled
              &&  window.wikEd.turnedOn
              &&  window.wikEd.useWikEd
              &&  window.wikEd.UpdateTextarea ) {
            window.wikEd.UpdateTextarea();
         }
         r  =  this.$textarea.val();
      }   // editing
      return r;
   };   // .gui.feed()



   BAK.gui.fence  =  function ( apply ) {
      // Draw box around entire autoBAK rectangle
      //    apply  -- true if to be shown, or false to remove
      // Uses:
      //    this
      //    >  .gui.$div
      //    jQuery().attr()
      // 2012-10-29 PerfektesChaos@de.wikipedia
      this.$div.attr( "style",
                      ( apply  ?    "border: solid 1px #606060;"
                                 + " border-bottom: solid 5px #606060;"
                                 + " padding: 0.5em;"
                                 + " margin-bottom: 2em;"
                                 :  null ) );
  };   // .gui.fence()



   BAK.gui.fiat  =  function ( action, add ) {
      // Create button
      // Precondition:
      //    action  -- identifier
      //    add     -- data identifier
      // Postcondition:
      //    Return html string; or ""
      // 2012-10-29 PerfektesChaos@de.wikipedia
      var r;
      switch ( action ) {
         case "ALL" :
         case "SHOW" :
            r  =  "<span"
                  + " style='font-size: 200%;"
                         + " font-weight: bold;"
                         + " color: #008000;'>*</span>";
            break;
         case "DELETE" :
            r  =  "<span"
                  + " style='font-size: 200%;"
                         + " font-weight: bold;"
                         + " color: #FF0000;'>X</span>";
            break;
         default:
            r  =  "";
      }   // switch action
      if ( r ) {
         r  =  "\n &nbsp; \n"
               + "<button type='button'"
                      + " id='autoBackup" + action
                              +  ( add ? add : "" )  +  "'>"
               + r
               + "</button>";
      }
      return r;
   };   // .gui.fiat()



   BAK.gui.fill  =  function () {
      // Fill current page with offers
      // Uses:
      //    >  .pages
      //    >  .pageID
      //    >  .gui.$msg
      //    >  .disk.stick
      //    >< .gui.$box
      //    >< .revID
      //    jQuery().find()
      //    jQuery().remove()
      //    mw.config.get()
      //    .gui.fence()
      //    .gui.face()
      // Remark: Used as event handler -- 'this' is not BAK
      // 2012-11-04 PerfektesChaos@de.wikipedia
      var page  =  BAK.pages[ BAK.pageID ],
          revs  =  false,
          i, n, s;
      BAK.gui.$msg.find( "#autoBackupALL" ).remove();
      if ( BAK.gui.$box ) {
         BAK.gui.$box.remove();
      }
      if ( page ) {
         BAK.gui.$box  =  $( "<div />" );
         BAK.gui.$msg.after( BAK.gui.$box );
         if ( ! BAK.revID ) {
            BAK.revID  =  mw.config.get( "wgCurRevisionId" );
         }
         for ( i in page ) {
            if ( BAK.disk.stick.indexOf( i )  <  0 ) {
               s  =  page[ i ].newest + " " + i;
               if ( revs ) {
                  revs.push( s );
               } else {
                  revs  =  [ s ];
               }
            }
         }   // for i in page
         n  =  revs.length;
         revs.sort();
         for ( i = revs.length-1;  i >= 0;  i-- ) {
            BAK.gui.folder( revs[ i ], page );
         }   // for i--
         BAK.gui.fence( true );
      } else {
         BAK.gui.face( false );
      }
  };   // .gui.fill()



   BAK.gui.finish  =  function ( access ) {
      // Define additional hook on standard editform button
      // Precondition:
      //    access  -- button ID
      // Uses:
      //    this
      //    >  .gui.$editform
      //    jQuery().find()
      //    jQuery().click()
      //    (.fresh)
      // 2012-10-29 PerfektesChaos@de.wikipedia
      var $btn  =  this.$editform.find( "#" + access );
      if ( $btn.length ) {
         $btn.click( BAK.fresh );
      }
   };   // .gui.finish()



   BAK.gui.flag  =  function ( apply, action ) {
      // Display error message similar to mw.util.jsMessage() MW 1.19
      // Precondition:
      //    apply   -- text keyword
      //    action  -- identifier for button, or false if error case
      // Uses:
      //    this
      //    >  .cnf.slang
      //    >< .cnf.self
      //    .cnf.feature()
      //    .gui.fiat()
      //    .gui.face()
      //    .gui.form()
      // 2012-11-03 PerfektesChaos@de.wikipedia
      var s;
      if ( ! BAK.cnf.self ) {
         BAK.cnf.self  =  BAK.cnf.feature( "BAKself" );
      }
      s  =  BAK.cnf.feature( apply );
      s  =  "<div id='autoBackup'"
            +   " style='border: solid "
            +                          (action ? 2 : 5)
            +                          "px #FF0000;"
            +          " padding: 0.5em;"
            +          (action ? ""
                               : " color: #FF0000;")
            +      "'>\n"
            +  "<span style='font-size: larger; font-weight: bold;'>"
            +  BAK.cnf.self + "</span> &nbsp;\n"
            +  (action ? ""
                       : "<span class='error'>")
            +  s
            +  (action ? "\n"
                       : "</span>\n")
            +  this.fiat( action )
            +  "</div>\n";
      this.face( s );
      if ( action ) {
         this.form( action, "" );
      }
   };   // .gui.flag()



   BAK.gui.folder  =  function ( access, album ) {
      // Insert single revision data into current page
      // Precondition:
      //    access  -- string  sortkey_space_symbol
      //    album   -- page revisions
      // Uses:
      //    this
      //    >  .gui.$box
      //    >  .revID
      //    jQuery().append()
      //    jQuery().find()
      //    .cnf.feature()
      //    mw.config.get()
      //    .gui.fiat()
      //    .gui.form()
      //    .util.focus()
      //    .jQuery()
      //    .gui.full()
      // 2012-11-06 PerfektesChaos@de.wikipedia
      var e  =  access.split( " " ),
          k  =  e[ 1 ],
          s  =  "autoBackupR" + k,
          i, w, $div, $textarea;
      this.$box.append( "<div id='" + s + "' />" );
      $div  =  this.$box.find( "#" + s );
      if ( parseInt( k, 10 )  ===  BAK.revID ) {
         s  =  k  +  " ("  +  BAK.cnf.feature( "thisPage" )  +  ")";
      } else {
         s  =  mw.config.get( "wgArticlePath" );
         s  =  "<a href='"
               + s.replace( /\$1/, "Special:PermanentLink/" ) + k
                     + "' target='_blank'>" + k + "</a>";
      }
      s  =  "<h4>oldid=" + s + "</h4>";
      s  =  s  +  this.fiat( "DELETE", k );
      //
      $div.append( s );
      this.form( "DELETE", k );
      e  =  album[ k ].history;
      for ( i = 0;  i < e.length;  i++ ) {
         k  =  e[ i ];
         w  =  k[ 1 ];
         s  =  "<h5>"  +  BAK.util.focus( k[ 0 ] )  +  "</h5>"
               + "<p>" + w.length + " bytes</p>";
         $div.append( s );
         $textarea  = $( "<textarea rows='5' readonly />" );
         $textarea.val( w );
         $div.append( $textarea );
      }   // for i++
      this.full();
   };   // .gui.folder()



   BAK.gui.form  =  function ( action, add ) {
      // Provide click action for button
      // Precondition:
      //    action  -- identifier
      //    add     -- data distinguisher, or ""
      // Uses:
      //    this
      //    >  .gui.$top
      //    jQuery().find()
      //    jQuery().click()
      //    (.fixed)
      // 2012-11-04 PerfektesChaos@de.wikipedia
      var fun  =  false,
          sign,
          $btn;
      switch ( action ) {
         case "ALL" :
            fun  =  this.fill;
            break;
         case "DELETE" :
            sign         =  "DELETE" + add;
            fun          =  function () {  BAK.fixed( add );  };
            BAK[ sign ]  =  fun;
            break;
         case "SHOW" :
            break;
      }   // switch action
      if ( fun ) {
         $btn  =  this.$top.find( "#autoBackup" + action + add );
         if ( $btn.length ) {
            $btn.click( fun );
         }
      }
   };   // .gui.form()



   BAK.gui.full  =  function () {
      // Display otherPages offers on GUI
      // Uses:
      //    this
      //    >  .pages
      //    >  .pageID
      //    >  .gui.$div
      //    >< .gui.reSpace
      //    jQuery().empty()
      //    .cnf.feature()
      //    jQuery().append()
      //    jQuery().find()
      //    mw.config.get()
      //    mw.util.wikiUrlencode()
      //    .util.focus()
      //    .gui.fence()
      // 2012-11-06 PerfektesChaos@de.wikipedia
      var e, g, i, p, s, u, $x;
      if ( BAK.pages ) {
         g  =  false;
         s  =  "" + BAK.pageID;
         for ( i in BAK.pages ) {
            if ( i !== s ) {
               p  =  BAK.pages[ i ];
               if ( p.subject ) {
                  if ( ! this.reSpace ) {
                     this.reSpace  =  new RegExp(" +", "g");
                  }
                  e  =  " "  +  p.subject.replace( this.reSpace, "_" );
               } else {
                  e  =  "";
               }
               e  =  p.newest + " " + i + e;
               if ( g ) {
                  g.push( e );
               } else {
                  g  =  [ e ];
               }
            }
         }   // for i
         if ( g ) {
            s  =  "<h3>"
                  + BAK.cnf.feature( "otherPages" )
                  + "</h3>\n"
                  + "<ul id='autoBackupPageList' />";
            this.$div.append( s );
            $x  =  this.$div.find( "#autoBackupPageList" );
            u   =  "<a target='_blank'"
                   + " href='" + mw.config.get( "wgScript" ) + "?";
            g.sort();
            for ( i = g.length - 1;  i >= 0;  i-- ) {
               e  =  g[ i ];
               p  =  e.split( " " );
               s  =  p[ 1 ];
               if ( s.substr( 0, 2 )  ===  "0_" ) {
                  s  =  s.substr( 2 );
                  s  =  u + "title=" + mw.util.wikiUrlencode( s )
                          + "&redirect=no'>" + s + "</a> ";
               } else {
                  s  =  "curid=" + u + "curid=" + s + "'>" + s + "</a> ";
                  if ( p[ 2 ] ) {
                     s  =  s  +  "("  +  p[ 2 ].replace( /_/g, " " )
                                               .replace( /</g, "&lt;" )
                              +  ") ";
                  }
               }
               s  =  "<li>"  +  s  +  BAK.util.focus( p[ 0 ] )
                     +  "</li>";
               $x.append( s );
            }   // for i--
         }
         this.fence( true );
      }
   };   // .gui.full()



   BAK.gui.further  =  function () {
      // Try to define additional hooks on buttons
      // Uses:
      //    this
      //    >  .gui.$editform
      //    .gui.finish()
      //    (.fresh)
      // 2012-11-03 PerfektesChaos@de.wikipedia
      if ( this.$editform ) {   // editing
         this.finish( "wpDiff" );
         this.finish( "wpPreview" );
         this.finish( "wpSave" );
         this.$editform.find( ".mw-summary" ).focusin( BAK.fresh );
      }
   };   // .gui.further()



   BAK.util.figure  =  function ( ask ) {
      // Is ask a positive number or zero?
      // Precondition:
      //    ask  -- string
      // Postcondition:
      //    Return true iff ask contains only digits
      // 2012-10-29 PerfektesChaos@de.wikipedia
		return  (/^[0-9]+$/).test( ask );
   };   // .util.figure()



   BAK.util.flat  =  function ( album, amount, arrange ) {
      // Reduce number of entries in object; remove lowest sorted
      // Precondition:
      //    album    -- object to be limited
      //    amount   -- maximum number of components within album
      //    arrange  -- Array with sort keys and IDs, or false
      //                every entry is string  sortkey_space_symbol
      // 2012-11-07 PerfektesChaos@de.wikipedia
      var e, i, k, s;
      if ( arrange ) {
         k  =  arrange.length - amount;
         if ( k > 0 ) {
            arrange.sort();
            for ( i = 0;  i <= k;  i++ ) {
               e  =  arrange[ i ];
               s  =  e.split( " " );
               delete album[ s[ 1 ] ];
            }   // for i
         }
      }
   };   // .util.flat()



   BAK.util.focus  =  function ( appoint ) {
      // Format ISO 8601 UTC timestamp according to local time
      // Precondition:
      //    appoint  -- Date object
      // Postcondition:
      //    Return ISO 8601 string in local time zone
      // Uses:
      //    this
      //    >  mw.user
      //    >  mw.user.options
      //    >< .cnf.justify
      //   .format()
      // 2014-03-28 PerfektesChaos@de.wikipedia
      var r  =  appoint + " UTC",
          s  =  typeof BAK.cnf.justify,
          g;
      if ( s !== "boolean"  &&  s !== "number" ) {
         BAK.cnf.justify  =  false;
         if ( typeof mw.user  ===  "object" ) {
            if ( mw.user.options ) {
               s  =  mw.user.options.get( "timecorrection" );
               if ( s ) {   // "System|120"
                  g  =  /\|?([0-9]+)$/.exec( s );
                  if ( g ) {
                     BAK.cnf.justify  =  parseInt( g[1], 10 );
                  }
               }
            }
         }
      }
      if ( BAK.cnf.justify === 0 ) {
         r  =  appoint;
      } else if ( typeof BAK.cnf.justify  ===  "number" ) {
         g  =  /^([0-9]+)-([01][0-9])-([0-3][0-9])T([0-2][0-9]):([0-6][0-9]):([0-6][0-9])$/.exec( appoint );
         if ( g ) {
            r  =  Date.UTC( parseInt( g[1], 10 ),
                            parseInt( g[2], 10 )  -  1,
                            parseInt( g[3], 10 ),
                            parseInt( g[4], 10 ),
                            parseInt( g[5], 10 )  +  BAK.cnf.justify,
                            parseInt( g[6], 10 ) );
            r  =  this.format( new Date( r ) );
         }
      }
		return  r;
   };   // .util.focus()



   BAK.util.format  =  function ( appoint ) {
      // Format timestamp according to ISO 8601
      // Precondition:
      //    appoint  -- Date object
      // Postcondition:
      //    Return ISO 8601 string in local time zone
      // 2012-10-29 PerfektesChaos@de.wikipedia
      var r  =  appoint.getUTCFullYear() + "-",
          i  =  appoint.getUTCMonth() + 1;
      if ( i < 10 ) {
			i	=  "0" + i;
      }
      r  =  r + i + "-";
      i  =  appoint.getUTCDate();
      if ( i < 10 ) {
			i	=  "0" + i;
      }
      r  =  r + i + "T";
      i  =  appoint.getUTCHours();
      if ( i < 10 ) {
			i	=  "0" + i;
      }
      r  =  r + i + ":";
      i  =  appoint.getUTCMinutes();
      if ( i < 10 ) {
			i	=  "0" + i;
      }
      r  =  r + i + ":";
      i  =  appoint.getUTCSeconds();
      if ( i < 10 ) {
			i	=  "0" + i;
      }
		return  r + i;
   };   // .util.format()



   BAK.family  =  function () {
      // Establish distinguishing page identifier
      // Postcondition:
      //    .pageID is available
      // Uses:
      //    >< .pageID
      //     < .pageName
      //    mw.config.get()
      // 2014-07-05 PerfektesChaos@de.wikipedia
      var env;
      if ( ! this.pageID ) {
         env  =  mw.config.get( [ "wgArticleId",
                                  "wgPageName" ] );
         this.pageID    =  env.wgArticleId;
         this.pageName  =  env.wgPageName;
         if ( this.pageID === 0 ) {
            this.pageID    =  "0_"
                              +  this.pageName.replace( /~/, "%7E" );
         }
      }
   };   // .family()



   BAK.fast  =  function () {
      // Check sessionStorage for quick page id test on view
      // Postcondition:
      //    Return true iff localStorage should be evaluated
      // Uses:
      //    this
      //    >  .disk.storage
      //    >  window.sessionStorage
      //    >< .pageID
      //    >< .pageName
      //     < .later
      //    mw.config.get()
      // 2013-08-23 PerfektesChaos@de.wikipedia
      var r  =  true,
          s  =  window.sessionStorage.getItem( this.disk.storage ),
          q;
      if ( s ) {
         r  =  ( s.length > 1 );
         if ( r ) {
            if ( ! this.pageID ) {
               this.pageID  =  mw.config.get( "wgArticleId" );
            }
            r  =  ( s.indexOf( "~" + this.pageID + "~" )  >=  0 );
            if ( ! r ) {
               if ( s.indexOf( "~0_" )  >=  0  ) {
                  if ( ! BAK.pageName ) {
                     BAK.pageName  =  mw.config.get( "wgPageName" );
                  }
                  q  =  "0_" + BAK.pageName.replace( /~/, "%7E" );
                  if ( s.indexOf( "~" + q + "~" )  >=  0 ) {
                     r           =  true;
                     this.later  =  true;
                  }
               }
            }
         }
      }
		return  r;
   };   // .fast()



   BAK.filter  =  function () {
      // Cleanup repository, discard old and supernumerous entries
      // Precondition:
      //    .pages is an object
      // Uses:
      //    this
      //    >  .disk.stick
      //    >  .cnf.maxAge
      //    >  .cnf.maxRev
      //    >  .cnf.maxPages
      //    >  .gui.$div
      //    >  .disk.storage
      //    >< .pages
      //    >< .now
      //    >< window.sessionStorage
      //    .cnf.fetch()
      //    .util.format()
      //    .util.flat()
      //    .gui.fence()
      // Requires: ECMA 262-3 § 11.8.5  (string comparison operators)
      // 2013-08-23 PerfektesChaos@de.wikipedia
      var lazy   =  true,
          store  =  "~",
          hist, page, pid, revs, rid, sift, stamp;
      this.cnf.fetch();
      if ( this.cnf.maxAge ) {
         if ( ! this.now ) {
            this.now  =  new Date();
         }
         sift  =  Date.UTC( this.now.getUTCFullYear(),
                            this.now.getUTCMonth(),
                            this.now.getUTCDate(),
                            this.now.getUTCHours() - this.cnf.maxAge,
                            0,
                            0 );
         sift  =  this.util.format( new Date( sift ) );
      } else {
         sift  =  "1970-01-01T00:00:00";
      }
      for ( pid in this.pages ) {
         page  =  this.pages[ pid ];
         if ( page.newest < sift ) {
            delete this.pages[ pid ];
         } else {
            revs  =  false;
            for ( rid in page ) {
               if ( this.disk.stick.indexOf( rid )  <  0 ) {
                  stamp  =  page[ rid ].newest;
                  if ( stamp > sift ) {
                     stamp  =  stamp + " " + rid;
                     if ( revs ) {
                        revs.push( stamp );
                     } else {
                        revs  =  [ stamp ];
                     }
                  } else {
                     delete page[ rid ];
                  }
               }
            }   // for rid in page
            if ( revs ) {
               page  =  page.newest + " " + pid;
               if ( hist ) {
                  hist.push( page );
               } else {
                  hist  =  [ page ];
               }
               this.util.flat( page, this.cnf.maxRev, revs );
               store  =  store + pid + "~";
               lazy   =  false;
            } else {
               delete this.pages[ pid ];
            }
         }
      }   // for pid in .pages
      this.util.flat( this.pages, this.cnf.maxPages, hist );
      window.sessionStorage.setItem( this.disk.storage, store );
      if ( lazy  &&  this.gui.$div ) {
         this.gui.fence();
      }
   };   // .filter()



   BAK.finalize  =  function ( already, add ) {
      // Remove trailing whitespace and compare to recent text
      // Precondition:
      //    already  -- old text, or empty if new
      //    add      -- new text
      // Postcondition:
      //    Return new text iff significant change, or false
      // Requires: JavaScript 1.3   String.charCodeAt
      // 2012-10-29 PerfektesChaos@de.wikipedia
      var k  =  add.length,
          r  =  add,
          i;
      for ( i = k;  i > 0;  i-- ) {
         if ( add.charCodeAt( i - 1 )  >  32 ) {
            break;   // for i--
         }
      }   // for i--
      if ( i > 1 ) {
         if ( i < k ) {
            r  =  add.substr( 0, i );
         }
         if ( already ) {
            if ( i === already.length ) {
               if ( r === already ) {
                  r  =  false;
               }
            }
         }
      } else {
         r  =  false;
      }
      return r;
   };   // .finalize()



   BAK.find  =  function () {
      // Launch API query for revisions of the same user
      // Uses:
      //    this
      //    >  .pageID
      //    >  .later
      //    >  .pageName
      //    mw.Api
      //    mw.util.wikiUrlencode()
      //    mw.config.get()
      //    (.founder)
      //    (.found)
      // 2015-07-07 PerfektesChaos@de.wikipedia
      var q  =  new mw.Api(),
          w  =  { action:     "query",
                  "continue": "",            // TEMP ??
                  pageids:    this.pageID,
                  prop:       "revisions",
                  rvlimit:    3,
                  rvuser:     mw.util.wikiUrlencode(
                                          mw.config.get( "wgUserName" ) )
                };
      if ( this.later ) {
         delete w.pageids;
         w.titles  =  this.pageName;
         q.get( w ).done( this.founder );
      } else {
         q.get( w ).done( this.found );
      }
   };   // .find()



   BAK.first  =  function () {
      // Opening edit page; check for pending attempts
      // Precondition:
      //    Page in edit::edit mode
      // Uses:
      //    this
      //    >  .pages
      //    >  mw.user
      //    >  mw.user.options
      //    >  .pageID
      //     < .gui.leading
      //     < .livePreview
      //    .disk.fetch()
      //    .find()
      //    jQuery.bind()
      //    mw.hook()
      //    (.fresh)
      // 2019-06-17 PerfektesChaos@de.wikipedia
      this.$editform  =  false;
      this.disk.fetch();
      if ( this.pages ) {
         if ( this.pages[ this.pageID ] ) {
            this.find();
         }
      }
      if ( typeof mw.user  ===  "object" ) {
         if ( mw.user.options  &&
              mw.user.options.get( "uselivepreview" ) ) {
            this.livePreview  =  true;
            $( mw ).bind( "LivePreviewDone", this.fresh );
         }
      }
      this.gui.leading  =  true;
      mw.hook( "wikipage.content" ).add( this.fresh );
   };   // .first()



   BAK.fixed  =  function ( apply ) {
      // Remove current page revision from storage and GUI
      // Precondition:
      //    apply  -- revision ID
      // Uses:
      //    this
      //    >  .gui.$top
      //    >  .pageID
      //    >< .pages
      //     < .disk.launch
      //    .flush()
      //    .gui.fence()
      //    .gui.face()
      // 2012-11-04 PerfektesChaos@de.wikipedia
      var p;
      this.gui.$top.find( "#autoBackupR" + apply ).remove();
      if ( this.pages ) {
         p  =  this.pages[ BAK.pageID ];
         if ( p ) {
            if ( p[ apply ] ) {
               delete p[ apply ];
               this.disk.launch  =  true;
               this.flush();
               if ( ! this.pages ) {
                  this.gui.fence( false );
               } else  if ( ! this.pages[ BAK.pageID ] ) {
                  this.gui.face( false );
               }
            }
         }
      }
   };   // .fixed()



   BAK.flush  =  function () {
      // Write repository
      // Uses:
      //    this
      //    >  .disk.launch
      //    >  .pages
      //    .filter()
      //    .disk.flush()
      // 2012-10-29 PerfektesChaos@de.wikipedia
      if ( this.disk.launch ) {
         if ( this.pages ) {
            this.filter();
         }
         this.disk.flush();
      }
   };   // .flush()



   BAK.folder  =  function () {
      // API: Display list of links to all other pages on current page
      // Uses:
      //    >  .gui.$div
      //    >  .gui.$msg
      //    >< .cnf.self
      //    .cnf.feature()
      //    jQuery().prepend()
      //    .gui.face()
      //    .gui.fill()
      //    .gui.filter()
      //    .gui.full()
      // Remark: May be used as event handler -- 'this' is not BAK
      // 2012-11-30 PerfektesChaos@de.wikipedia
      BAK.gui.face();
      if ( ! BAK.gui.$msg ) {
         if ( ! BAK.cnf.self ) {
            BAK.cnf.self  =   BAK.cnf.feature( "BAKself" );
         }
         BAK.gui.$div.prepend( "<h2>" + BAK.cnf.self + "</h2>" );
      }
      BAK.gui.fill();
      BAK.gui.filter();
      BAK.gui.full();
   };   // .folder()



   BAK.follow  =  function () {
      // Check whether current page displays saved edit
      // Precondition:
      //    Page in view mode
      // Uses:
      //    this
      //    >  .pages
      //    >  .later
      //    >  .pageID
      //     < .revID
      //    .disk.fetch()
      //    .find()
      //    mw.config.get()
      //    .gui.flag()
      // 2012-11-30 PerfektesChaos@de.wikipedia
      var h, i, p;
      this.disk.fetch();
      if ( this.later ) {
         this.find();
      } else if ( this.pages ) {
         p  =  this.pages[ this.pageID ];
         if ( p ) {
            this.revID  =  mw.config.get( "wgCurRevisionId" );
            for ( h in p ) {
               i  =  parseInt( h, 10 );
               if ( i ) {
                  if ( this.revID > i ) {
                     this.find();
                     break;   // for h
                  } else if ( this.revID === i ) {
                     this.gui.flag( "pending", "ALL" );
                     break;   // for h
                  }
               }
            }   // for h in p
         }
      }
   };   // .follow()



   BAK.found  =  function ( arrived ) {
      // Postprocess in page view after ajax request
      // Precondition:
      //    arrived  -- JSON result of ajax query
      //    Previously it has been detected that revID is open snapshot.
      // Uses:
      //    >  .pageID
      //    >  .revID
      //    >< .pages
      //     < .disk.launch
      //    .flush()
      // Remark: Used as event handler -- 'this' is not BAK
      // 2012-10-29 PerfektesChaos@de.wikipedia
      var query   =  ( typeof arrived   ===  "object" ),
          learnt  =  false,
          i;
      if ( query ) {
         query  =  arrived.query;
         if ( query ) {
            query  =  query.pages[ BAK.pageID ];
            if ( query ) {
               query  =  query.revisions;
               if ( query ) {
                  for ( i = 0;  i < query.length;  i++ ) {
                     if ( query[i].revid === BAK.revID ) {
                        learnt  =  true;
                        delete BAK.pages[ BAK.pageID ];
                        BAK.disk.launch  =  true;
                        break;  //  for i
                     }
                  }   //  for i
               }
            }
         }
      }
      BAK.flush();
      if ( ! learnt ) {
         BAK.gui.flag( "pending", "ALL" );
      }
   };   // .found()



   BAK.founder  =  function ( arrived ) {
      // Postprocess a created page in page view after ajax request
      // Precondition:
      //    arrived  -- JSON result of ajax query
      //    Previously it has been detected revID=0 was open snapshot.
      // Uses:
      //    >  .pageName
      //    >< .pages
      //     < .pageID
      //     < .disk.launch
      //    .flush()
      // Remark: Used as event handler -- 'this' is not BAK
      // 2012-12-02 PerfektesChaos@de.wikipedia
      var query   =  ( typeof arrived   ===  "object" ),
          learnt  =  false;
      if ( query ) {
         query  =  arrived.query;
         if ( query ) {
            query  =  query.pages[ BAK.pageID ];
            if ( query ) {
               BAK.pageID  =  "0_"
                              +  BAK.pageName.replace( /~/, "%7E" );
               delete BAK.pages[ BAK.pageID ];
               BAK.disk.launch  =  true;
               learnt  =  true;
            }
         }
      }
      BAK.flush();
      if ( ! learnt ) {
         BAK.gui.flag( "pending", "ALL" );
      }
   };   // .founder()



   BAK.fresh  =  function () {
      // API: Add snapshot to storage if content changed
      // Precondition:
      //    Start of preview/diff mode, or time-triggered
      // Uses:
      //    >  .pageID
      //    >  .pageName;
      //    >  .cnf.maxHist
      //    >  .cnf.msec
      //    >< .timeoutID
      //     < .revID
      //     < .now
      //     < .disk.launch
      //    .gui.feed()
      //    .family()
      //    mw.config.get()
      //    .disk.fetch()
      //    .finalize()
      //    .util.format()
      //    .cnf.fetch()
      //    .flush()
      // Remark: Used as event handler -- 'this' is not BAK
      // 2013-02-17 PerfektesChaos@de.wikipedia
      var shot   =  BAK.gui.feed(),
          page, revs, stamp;
      if ( shot ) {
         BAK.family();
         BAK.revID  =  mw.config.get( "wgCurRevisionId" );
         if ( ! BAK.pages ) {
            BAK.disk.fetch();
         }
         if ( ! BAK.pages ) {
            BAK.pages  =  { };
         }
         if ( ! BAK.pages[ BAK.pageID ] ) {
            BAK.pages[ BAK.pageID ]  =  { };
         }
         page  =  BAK.pages[ BAK.pageID ];
         if ( ! page[ BAK.revID ] ) {
            page[ BAK.revID ]  =  { };
         }
         revs  =  page[ BAK.revID ].history;
         if ( revs ) {
            shot  =  BAK.finalize( revs[0][1], shot );
            if ( shot ) {
               BAK.now  =  new Date();
               stamp    =  BAK.util.format( BAK.now );
               BAK.cnf.fetch();
               if ( revs.length >= BAK.cnf.maxHist - 1 ) {
                  revs.pop();
               }
               revs.unshift( [ stamp, shot ] );
            }
         } else {
            shot  =  BAK.finalize( false, shot );
            if ( shot ) {
               BAK.now  =  new Date();
               stamp    =  BAK.util.format( BAK.now );
               revs     =  [ [ stamp, shot ] ];
            }
         }
         if ( shot ) {
            page[ BAK.revID ].history  =  revs;
            page[ BAK.revID ].newest   =  stamp;
            page.newest                =  stamp;
            page.subject               =  BAK.pageName;
            BAK.pages[ BAK.pageID ]    =  page;
            BAK.disk.launch            =  true;
            BAK.flush();
         }
         if ( BAK.cnf.msec ) {
            if ( BAK.timeoutID ) {
               window.clearTimeout( BAK.timeoutID );
            }
            BAK.timeoutID  =  window.setTimeout( BAK.fresh,
                                                 BAK.cnf.msec );
         }
      }
   };   // .fresh()



   BAK.further  =  function () {
      // Additional triggers in edit mode, to be called only once
      // Uses:
      //    this
      //    >  .gui.$editform
      //    >  .cnf.msec
      //    >< .learnt
      //    .gui.further()
      //    .cnf.fetch()
      //    (.fresh)
      // 2012-10-29 PerfektesChaos@de.wikipedia
      if ( ! this.learnt ) {   // only once
         this.learnt   =  true;
         if ( this.gui.$editform ) {   // editing
            this.gui.further();
            this.cnf.fetch();
            if ( this.cnf.msec ) {
               window.setTimeout( this.fresh, this.cnf.msec );
            }
         }
      }
   };   // .further()



   BAK.fire  =  function () {
      // Start page processing; check whether situation is interesting
      // Uses:
      //    >  .disk.self
      //     < .disk.storage
      //     < .learn
      //     < .gui.leading
      //    mw.config.get()
      //    .follow()
      //    mw.util.getParamValue()
      //    mw.hook()
      //    (.firing)
      // Remark: Used as event handler -- 'this' is not BAK
      // 2017-01-04 PerfektesChaos@de.wikipedia
      var env  =  mw.config.get( [ "wgAction",
                                   "wgIsArticle",
                                   "wgUserName" ] );
      BAK.disk.storage  =  BAK.disk.self
                           + " " +  env.wgUserName;
      if ( env.wgIsArticle ) {
         if ( env.wgAction === "view"  &&
              BAK.fast() ) {
            BAK.follow();
         }
      } else {
         BAK.start  =  mw.util.getParamValue( "action" );
         if ( BAK.start ) {
            BAK.learn  =  true;
            if ( "|edit|submit|".indexOf( BAK.start )  >  0 ) {
               mw.hook( "wikipage.content" ).add( BAK.firing );
            }
         }
      }
   };   // .fire()



   BAK.firing  =  function ( $all ) {
      // Start content processing
      // Precondition:
      //    $all  -- mw.util.$content
      // Uses:
      //    >  .start
      //     < .gui.leading
      //    .fast()
      //    .fresh()
      //    .further()
      //    .first()
      // Remark: Used as event handler -- 'this' is not BAK
      // 2019-07-01 PerfektesChaos@de.wikipedia
      BAK.$page = $all;
      switch ( BAK.start ) {
         case "submit" :
            BAK.gui.leading  =  true;
            BAK.fresh();
            BAK.further();
            break;
         case "edit" :
            BAK.first();
            BAK.further();
      }   // switch .start
   };   // .firing()



   function first() {
      // Initiate resource loading
      // Uses:
      //    >  .type
      //     < .error
      //    mw.loader.getState()
      //    mw.loader.using()
      //    mw.loader.state()
      //    (.fire)
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var signature = "ext.gadget." + BAK.type,
          rls;
      if ( mw.loader.getState( signature )  !==  "ready" ) {
         rls = { };
         rls[ signature ] = "ready";
         mw.loader.state( rls );
         if ( mw.config.get( "wgNamespaceNumber" )  >=  0 ) {
            mw.loader.using( [ "user",
                               "mediawiki.api",
                               "mediawiki.user",
                               "mediawiki.util" ],
                             BAK.fire );
         }
      }
   }   // first()



   first();
}( window.mediaWiki, window.jQuery ) );



// Emacs
// Local Variables:
// coding: utf-8-dos
// fill-column: 80
// End:

/// EOF </nowiki>   autoBackup/d.js