User:PerfektesChaos/js/preferencesGadgetOptions/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/preferencesGadgetOptions/d.js
/// 2021-10-08 PerfektesChaos@de.wikipedia
// Preferences utilities, gadget options and configuration form
// ResourceLoader:  compatible;
//    dependencies: user.options, mediawiki.user, mediawiki.util
/// Fingerprint: #0#0#
/// @license GPL [//www.mediawiki.org/w/COPYING] (+GFDL, LGPL, CC-BY-SA)
/// <nowiki>
/* global window: false, JSON:false                                    */
/* jshint forin:false,
          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.1,
       Signature  =  "preferencesGadgetOptions",
       PREGO      =  { signature: "ext.gadget." + Signature,
                       special:   "Blankpage",
                       type:      Signature,
                       vsn:       Version,
                       api:       { },
                       cache:     { },
                       date:      { },
                       dialog:    { },
                       hook:      { },
                       lang:      { },
                       option:    { },
                       page:      { } };



   /*
    * 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)
   //           MediaWiki   1.26  (mw.storage, mw.libs, jQuery core)



   function fire() {
      // Environment initialized
      // Precondition:
      //    user.options and basic resources have been loaded
      // Postcondition:
      //    Ready to be used
      // Uses:
      //    >  .type
      //     < .doc
      //     < .lang.user
      //     < .local
      //    mw.user.isAnon()
      //    mw.loader.using()
      //    .hook.fire()
      //    .finalize()
      //    (.cache.first)
      // Remark: Used as event handler -- 'this' is not PREGO
      // 2018-05-02 PerfektesChaos@de.wikipedia
      PREGO.doc        =  "[[w:en:User:PerfektesChaos/js/" + PREGO.type
                          + "]]";
      PREGO.lang.user  =  { "als" :  [ "de", "fr" ],
                            "bar" :  [ "de" ],
                            "br"  :  [ "fr" ],
                            "cel" :  [ "fr" ],
                            "dsb" :  [ "hsb", "de" ],
                            "fro" :  [ "fr" ],
                            "frp" :  [ "fr" ],
                            "frr" :  [ "de", "nl" ],
                            "fur" :  [ "it" ],
                            "fy" :   [ "nl" ],
                            "gsw" :  [ "de" ],
                            "hsb" :  [ "dsb", "de" ],
                            "ksh" :  [ "de" ],
                            "lb" :   [ "de" ],
                            "li" :   [ "nl" ],
                            "lij" :  [ "it" ],
                            "lld" :  [ "it" ],
                            "lmo" :  [ "it" ],
                            "nap" :  [ "it" ],
                            "nds" :  [ "de", "nl" ],
                            "nn" :   [ "no", "da", "sv" ],
                            "no" :   [ "nn", "da", "sv" ],
                            "oc"  :  [ "fr" ],
                            "pdc" :  [ "de" ],
                            "pdt" :  [ "de" ],
                            "pfl" :  [ "de" ],
                            "pms" :  [ "it" ],
                            "sc"  :  [ "it" ],
                            "sli" :  [ "de" ],
                            "stq" :  [ "de" ],
                            "vec" :  [ "it" ],
                            "vls" :  [ "nl" ],
                            "vmf" :  [ "de" ],
                            "wa"  :  [ "fr" ]
                          //"simple": Universal fallback "en"
                          };   // 2017-11-11 PerfektesChaos@de.wikipedia
      PREGO.local  =  mw.user.isAnon();
      if ( PREGO.local ) {
         mw.loader.using( [ "mediawiki.storage" ],
                          PREGO.cache.first );
      } else {
         PREGO.finalize();
      }
      PREGO.hook.fire();
   }   // fire()



   function first() {
      // Autorun
      // Uses:
      //    >  .signature
      //    >  .type
      //    >  mw.libs
      //    >< PREGO
      //    mw.loader.getState()
      //    mw.loader.state()
      //    mw.loader.using()
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var launch, prev, rls;
      if ( mw.loader.getState( PREGO.signature )  !==  "ready" ) {
         rls = { };
         rls[ PREGO.signature ] = "ready";
         mw.loader.state( rls );
         if ( typeof mw.libs[ PREGO.type ]  !==  "object"   ||
              !      mw.libs[ PREGO.type ] ) {
            mw.libs[ PREGO.type ]  =  PREGO;
            launch  =  true;
         } else {
            prev    =  mw.libs[ PREGO.type ];
            launch  =  ( typeof prev.vsn  ===  "undefined" );
            if ( launch ) {
               $.extend( prev, PREGO );
               PREGO  =  prev;
            }
         }
         if ( launch ) {
            mw.loader.using( [ "user",
                               "user.options",
                               "mediawiki.user" ],
                             fire );
         }
      }
   }   // first()



   PREGO.api.failed  =  function ( about, add, aborted ) {
      // API attempt failed
      // Precondition:
      //    about    -- reason
      //    add      -- object with details
      //    aborted  -- callback error function (optional), or false
      // Postcondition:
      //    message is displayed
      // Uses:
      //    mw.hook()
      //    (.api.flop)
      // Remark: Used as event handler -- 'this' is not PREGO.api
      // 2013-12-19 PerfektesChaos@de.wikipedia
      var s  =  "API error";
      if ( about ) {
         s  =  s + "\n\n" + about;
      }
      if ( add ) {
         if ( add.error ) {
            if ( add.error.info ) {
               s  =  s + ":\n" + add.error.info;
            }
         }
      }
      s  =  s + "\n\n* Login session might have been expired.";
      if ( typeof aborted  ===  "function" ) {
         aborted.call( PREGO, s );
      }
      mw.hook( "wikipage.content" ).add( function () {
                                                     PREGO.api.flop( s );
                                                     } );
   };   // .api.failed()



   PREGO.api.fiat  =  function ( api, apply ) {
      // Store one element in queue as preference
      // Postcondition:
      //    api     -- mw.Api object
      //    apply   -- request details
      //               [0] preference (without "userjs-")
      //               [1] value
      //               [2] callback function (optional)
      //               [3] error function (optional)
      //               [4] jQuery object of option/gadget
      //    mw.user.tokens.get()
      //    (.api.follow)
      //    (.api.failed)
      // 2021-10-08 PerfektesChaos@de.wikipedia
      var pars =  { "action":     "options",
                    "token":      mw.user.tokens.get( "csrfToken" ),
                    "optionname": apply[ 0 ] };
      if ( apply[ 1 ]  !==  null ) {
         pars.optionvalue  =  apply[ 1 ];
      }
      api.post( pars )
         .done( function ( d, t, j ) {
                                     PREGO.api.follow( true,
                                                       apply[ 4 ] );
                                     if ( apply[ 2 ] ) {
                                        apply[ 2 ].call( null, d, t, j );
                                     }
                } )
         .fail( function ( d, t, j ) {
                                     PREGO.api.failed( d );
                                     PREGO.api.follow( false,
                                                       apply[ 4 ] );
                                     if ( apply[ 3 ] ) {
                                        apply[ 3 ].call( null, d, t, j );
                                     }
                } );
   };   // .api.fiat()



   PREGO.api.fire  =  function ( assign, apply, any, after, aborted, at ) {
      // Initiate storing an option value on server
      // Precondition:
      //    assign    -- string with option/gadget identification
      //    apply     -- string value, or null for deletion
      //    any       -- assign is mediawiki option name, or false
      //    after     -- callback function (optional), or false
      //    aborted   -- error function (optional), or false
      //    at        -- issued by gadget dialog form
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    this
      //    >  .page.$throbber
      //    >  mw.util.$content
      //    >< .api.queue
      //    .page.fragment()
      //    .page.factory()
      //    mw.loader.using()
      //    (.api.forward)
      // 2021-10-08 PerfektesChaos@de.wikipedia
      var $api  =  false,
          r, s, task;
      if ( typeof assign  ===  "string" ) {
         if ( /[^a-zA-Z0-9_-]/.test( assign ) ) {
            r  =  "invalid character in key 'assign'";
         } else if ( apply === null
                     ||   typeof apply  ===  "string" ) {
            r  =  false;
         } else {
            r  =  "invalid type for value 'apply'";
         }
      } else {
         r  =  "key 'assign' not a string";
      }
      if ( ! r ) {
         if ( at ) {
            s     =  "#" + PREGO.page.fragment( assign );
            $api  =  mw.util.$content.find( s );
            if ( $api.length ) {
               $api.empty();
               if ( ! PREGO.page.$throbber ) {
                  PREGO.page.factory( "$throbber" );
               }
               $api.append( PREGO.page.$throbber.clone() );
            }
         }
         task  =  [ ( any  ?  assign  :  "userjs-" + assign ),
                    apply,
                    false,
                    false,
                    $api ];
         if ( typeof after  ===  "function" ) {
            task[ 2 ]  =  after;
         }
         if ( typeof aborted  ===  "function" ) {
            task[ 3 ]  =  aborted;
         }
         if ( this.queue ) {
            this.queue.push( task );
         } else {
            this.queue  =  [ task ];
         }
         mw.loader.using( [ "mediawiki.api" ],
                          this.forward );
      }
      return r;
   };   // .api.fire()



   PREGO.api.fix  =  function ( arrived, assign, advanced, after, assume ) {
      // Update mw object and notify user by callback
      // Precondition:
      //    arrived  -- JSON result of ajax query
      //    assign   -- string with option identification
      //    advanced  -- assign is mediawiki option name, or false
      //                 0  -- mediawiki option
      //                 1  -- userjs single value
      //                 2  -- Gadgets
      //    after    -- callback function (optional)
      //    assume   -- fallback value, if undefined or failure
      // Uses:
      //    mw.user.options.set()
      //    .get()
      //    .fetch()
      // Remark: Used as event handler -- 'this' is not PREGO.api
      // 2013-09-19 PerfektesChaos@de.wikipedia
      var q  =  arrived.query,
          s;
      if ( q ) {
         q  =  q.userinfo;
         if ( q ) {
            q  =  q.options;
            if ( q ) {
               s  =  ( advanced  ?  "userjs-" + assign  :  assign );
               q  =  q[ s ];
               mw.user.options.set( s,  q );
               if ( typeof after  ===  "function" ) {
                  switch ( advanced ) {
                     case 1:
                        q  =  PREGO.get( assign, assume );
                        break;
                     case 2:
                        q  =  PREGO.fetch( assign, assume );
                        break;
                  }   // switch advanced
                  after.call( PREGO, q );
               }
            }
         }
      }
   };   // .api.fix()



   PREGO.api.flop  =  function ( about ) {
      // Display error message
      //    about  -- message
      // Uses:
      //    >  .type
      //    >  mw.util.$content
      // Remark: Used as event handler -- 'this' is not PREGO.api
      // 2013-12-29 PerfektesChaos@de.wikipedia
      var $div   =  $( "<div>" ),
          $span  =  $( "<span>" );
      $div.attr( "class", "error" );
      $span.text( "<span>" + PREGO.type + "<br />" + about + "</span>" );
      $div.append( $span );
      mw.util.$content.prepend( $div );
   };   // .api.flop()



   PREGO.api.follow  =  function ( achieved, $apply ) {
      // Confirm success
      // Precondition:
      //    achieved  -- true: success
      //    $apply    -- container for icons
      // 2015-09-25 PerfektesChaos@de.wikipedia
      var src;
      if ( $apply   &&  $apply.length ) {
         src  =  ( achieved ? "$stored" : "$denied" );
         $apply.empty();
         if ( ! PREGO.page[ src ] ) {
            PREGO.page.factory( src );
         }
         $apply.append( PREGO.page[ src ].clone() );
      }
   };   // .api.follow()



   PREGO.api.forward  =  function () {
      // Store all elements in queue as preferences
      // Uses:
      //    >  mw.Api
      //    >< .api.queue
      //       [0] preference (without "userjs-")
      //       [1] value
      //       [2] callback function (optional)
      //       [3] error function (optional)
      //    .api.fiat()
      // Remark: Used as event handler -- 'this' is not PREGO.api
      // 2021-10-08 PerfektesChaos@de.wikipedia
      var i, n, request;
      if ( PREGO.api.queue ) {
         n  =  PREGO.api.queue.length;
         if ( n > 0 ) {
            request  =  new mw.Api();
            for ( i = 0;  i < n;  i++ ) {
               PREGO.api.fiat( request, PREGO.api.queue[ i ] );
            }   // for i
         }
         PREGO.api.queue  =  false;
      }
   };   // .api.forward()



   PREGO.api.fresh  =  function ( assign, advanced, assume, after, aborted ) {
      // Retrieve current option value and update mw object
      // Precondition:
      //    assign    -- string with option identification
      //    advanced  -- assign is mediawiki option name, or false
      //                 0  -- mediawiki option
      //                 1  -- userjs single value
      //                 2  -- Gadgets
      //    assume    -- fallback value, if undefined or failure
      //    after     -- callback function, or else
      //    aborted   -- error callback function (optional), or false
      // Uses:
      //    >  mw.Api
      //    (.api.freshed)
      //    (.api.failed)
      // 2013-09-21 PerfektesChaos@de.wikipedia
      var q  =  new mw.Api();
      q.get( { "action": "query",
               "meta":   "userinfo",
               "uiprop": "options" } )
       .done( function ( arrived ) {
               PREGO.api.fix( arrived, assign, advanced, after, assume );
                                   } )
       .fail( function ( about, add ) {
                                 PREGO.api.failed( about, add, aborted );
                                   } );
   };   // .api.fresh()



   PREGO.cache.first =  function () {
      // Initialize anonymous localStorage
      // Precondition:
      //    mediawiki.storage is available
      // Postcondition:
      // Uses:
      //    >  .type
      //     < .cache.data
      //    mw.storage.get{}
      //    JSON.parse()
      //    mw.user.options.set()
      //    .finalize()
      // Remark: Used as event handler -- 'this' is not PREGO.cache
      // 2016-10-11 PerfektesChaos@de.wikipedia
      var s  =  mw.storage.get( PREGO.type );
      if ( s ) {
         try {
            PREGO.cache.data  =  JSON.parse( s );
         } catch(e) {
            // why ever * corrupted by anyone
            PREGO.cache.data  =  false;
         }
         if ( typeof PREGO.cache.data  ===  "object"
              &&     PREGO.cache.data ) {
            if ( typeof PREGO.cache.data.userjs  ===  "object"
                 &&     PREGO.cache.data.userjs ) {
               for ( s in PREGO.cache.data.userjs )  {
                  mw.user.options.set( "userjs-" + s,
                                       PREGO.cache.data.userjs[ s ] );
               }   // for s in userjs
            } else {
               PREGO.cache.data.userjs  =  false;
            }
            // other components and preferences might follow
         } else {
            PREGO.cache.data  =  { };
         }
      }
      PREGO.finalize();
   };   // .cache.first()



   PREGO.cache.fill =  function ( assign, apply ) {
      // Store user option with localStorage
      // Precondition:
      //    assign  -- string with option/gadget identification
      //    apply   -- JSONified value
      // Postcondition:
      //    Returns boolean on success
      // Uses:
      //    this
      //    >  .cache.data
      //    >  .type
      //    >  mw.util.$content
      //    JSON.stringify()
      //    mw.storage.set()
      //    .page.fragment()
      //    .api.follow()
      // 2015-09-25 PerfektesChaos@de.wikipedia
      var r  =  this.data,
          s;
      if ( r ) {
         if ( typeof this.data.userjs  !==  "object"
              ||   ! this.data.userjs ) {
            this.data.userjs  =  { };
         }
         this.data.userjs[ assign ]  =  apply;
         r  =  mw.storage.set( PREGO.type,
                               JSON.stringify( this.data ) );
         s  =  "#" + PREGO.page.fragment( assign );
         PREGO.api.follow( r,  mw.util.$content.find( s ) );
      }
      return r;
   };   // .cache.fill()



   PREGO.cache.free =  function ( assign ) {
      // Store user option with localStorage
      // Precondition:
      //    assign  -- string with option/gadget identification
      //    apply   -- JSONified value
      // Postcondition:
      //    Returns boolean on success
      // Uses:
      //    this
      //    >  .cache.data
      //    >  .type
      //    JSON.stringify()
      //    mw.storage.set()
      // 2015-09-12 PerfektesChaos@de.wikipedia
      var r  =  this.data;
      if ( r ) {
         if ( typeof r.userjs  ===  "object"   &&
              typeof r.userjs[ assign ]  !==  "undefined" ) {
            delete this.data.userjs.assign;
            r  =  mw.storage.set( PREGO.type,
                                  JSON.stringify( this.data ) );
         } else {
            r  =  true;
         }
      } else {
         r  =  false;
      }
      return r;
   };   // .cache.free()



   PREGO.date.format8601  =  function ( age ) {
      // Format Date object according to ISO8601
      // Precondition:
      //    age  -- Date object (since 1970)
      // Postcondition:
      //    Returns string (human readable format)
      // 2013-09-16 PerfektesChaos@de.wikipedia
      var jY = age.getUTCFullYear(),
          jM = age.getUTCMonth() + 1,
          jD = age.getUTCDate(),
          kH = age.getUTCHours(),
          kM = age.getUTCMinutes(),
          kS = age.getUTCSeconds(),
          kX = age.getUTCMilliseconds();
      if ( jM < 10 ) {
         jM  =  "0" + jM;
      }
      if ( jD < 10 ) {
         jD  =  "0" + jD;
      }
      if ( kH < 10 ) {
         kH  =  "0" + kH;
      }
      if ( kM < 10 ) {
         kM  =  "0" + kM;
      }
      if ( kS < 10 ) {
         kS  =  "0" + kS;
      }
      if ( kX < 100 ) {
         if ( kX < 10 ) {
            kX  =  "00" + kX;
         } else {
            kX  =  "0" + kX;
         }
      }
      return jY + "-" + jM + "-" + jD
             + " "
             + kH + ":" + kM + ":" + kS + "."+ kX
             + "Z";
   };   // .date.format8601()



   PREGO.date.from8601  =  function ( age ) {
      // Retrieve Date object from ISO8601 string (human readable format)
      // Precondition:
      //    age  -- string (since 1970)
      // Postcondition:
      //    Returns Date object
      // 2013-09-28 PerfektesChaos@de.wikipedia
      return new Date( parseInt( age.substr(  0, 4 ),  10 ),
                       parseInt( age.substr(  5, 2 ),  10 )   -   1,
                       parseInt( age.substr(  8, 2 ),  10 ),
                       parseInt( age.substr( 11, 2 ),  10 ),
                       parseInt( age.substr( 14, 2 ),  10 ),
                       parseInt( age.substr( 17, 2 ),  10 ),
                       parseInt( age.substr( 20, 3 ),  10 ) );
   };   // .date.from8601()



   PREGO.dialog.factory  =  function ( about ) {
      // Create form
      // Precondition:
      //    about  -- object with gadget description
      //              >  .$item
      //              >  .opts
      //              >  .script
      //              >  .fiat
      //               < .selector
      //               < .$pop
      //    document.ready
      // Uses:
      //    >  .page.$optsForm
      //    >  .page.$btnClose
      //    >  .page.$btnSubmit
      //    >  .page.ltr
      //    .page.factory()
      //    .option.factory()
      //    .page.fragment()
      //    .page.feature()
      //    (.dialog.forward)
      // 2019-02-13 PerfektesChaos@de.wikipedia
      var i, n, $e;
      if ( typeof about.opts  ===  "object"   &&   about.opts ) {
         n  =  about.opts.length;
         if ( n ) {
            about.selector  =  PREGO.type + "-form_" + about.script;
            if ( ! PREGO.page.$optsForm ) {
               PREGO.page.factory( "$optsForm" );
            }
            about.$pop  =  PREGO.page.$optsForm.clone();
            about.$pop.attr( "id", about.selector );
            if ( ! PREGO.page.$btnClose ) {
               PREGO.page.factory( "$btnClose" );
            }
            $e  =  PREGO.page.$btnClose.clone();
            $e.click( function () {   PREGO.dialog.flat( about );
                                  } );
            about.$pop.append( $e );
            for ( i = 0;  i < n;  i++ ) {
               PREGO.option.factory( about.selector,
                                     about.opts[ i ],
                                     about.$pop );
            }   // for i
            if ( ! PREGO.page.$btnSubmit ) {
               PREGO.page.factory( "$btnSubmit" );
            }
            about.$submit  =  PREGO.page.$btnSubmit.clone();
            about.$submit.click( function () {
                                    PREGO.dialog.forward( about );
                                 } );
            $e  =  $( "<div>");
            $e.append( about.$submit );
            about.$pop.append( $e );
            $e  =  $( "<span>" );
            $e.attr( { "class":  PREGO.page.fragment(),
                       "id":     PREGO.page.fragment( about.script )
                     } );
            if ( PREGO.page.ltr ) {
               $e.css( "margin-left", "2em" );
               about.$submit.after( $e );
            } else {
               $e.css( "margin-right", "2em" );
               about.$submit.before( $e );
            }
            if ( typeof about.fiat  ===  "function" ) {
               about.fiat.call( about, about.$submit );
            }
         } else {
            $e  =  $( "<div>");
            $e.attr( "class", "error" );
            $e.text( "empty .opts in config" );
            about.$pop.append( $e );
         }
      }
      about.$item.append( about.$pop );
   };   // .dialog.factory()



   PREGO.dialog.fetch  =  function ( about ) {
      // Retrieve object with all current values in form
      // Precondition:
      //    about  -- object with gadget description
      // Postcondition:
      //    Returns all current values as of user.options.userjs
      // Uses:
      //    .option.fetch()
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var i, n, o, v, r;
      if ( about.opts ) {
         r  =  { };
         n  =  about.opts.length;
         for ( i = 0;  i < n;  i++ ) {
            o  =  about.opts[ i ];
            v  =  undefined;
            switch ( o.type ) {
               case "checkbox" :
                  v  =  o.$checkbox.prop( "checked" );
                  break;
               case "radio" :
               case "multi" :
               case "select" :
                  v  =  PREGO.option.fetch( o );
                  break;
               case "text" :
                  v  =  $.trim( o.$text.val() );
                  break;
            }   // switch .type
            if ( v !== undefined ) {
               r[ o.signature ]  =  v;
            }
         }   // for i
      } else {
         r  =  null;
      }
      return r;
   };   // .dialog.fetch()



   PREGO.dialog.fired  =  function ( about ) {
      // Expand form, create if not yet built
      // Precondition:
      //    about  -- object with gadget spec
      // Uses:
      //    .dialog.fresh()
      //    .dialog.factory()
      // 2019-02-13 PerfektesChaos@de.wikipedia
      if ( about.opts   &&   typeof about.opts  ===  "object" ) {
         PREGO.dialog.fresh( about );
      }
      if ( about.$pop ) {
         about.$pop.css( "display", "block" );
      } else {
         PREGO.dialog.factory( about );
      }
      about.$button.css( "display", "none" );
   };   // .dialog.fired()



   PREGO.dialog.flat  =  function ( about ) {
      // Hide form
      // Precondition:
      //    about  -- object with gadget spec
      // Uses:
      //    .page.fragment()
      // 2019-02-13 PerfektesChaos@de.wikipedia
      about.$pop.css( "display", "none" );
      about.$button.css( "display", "inline" );
      about.$pop.find( "." + PREGO.page.fragment() ).empty();
   };   // .dialog.flat()



   PREGO.dialog.forward  =  function ( about ) {
      // Perform storage; request API start
      // Precondition:
      //    about  -- object with gadget spec
      // Uses:
      //    .dialog.fetch()
      //    .forward()
      // 2019-02-13 PerfektesChaos@de.wikipedia
      var failure, fed, values;
      values  =  PREGO.dialog.fetch( about );
      if ( values ) {
         if ( typeof about.fed  ===  "function" ) {
            fed  =  about.fed;
         } else {
            fed  =  false;
         }
         if ( typeof about.failure  ===  "function" ) {
            failure  =  about.failure;
         } else {
            failure  =  false;
         }
         PREGO.forward( about.script, values, fed, failure );
         if ( typeof about.filled  ===  "function" ) {
            about.filled.call( about, values );
         }
      }
   };   // .dialog.forward()



   PREGO.dialog.fresh  =  function ( about ) {
      // Preset all option values from user.options
      // Precondition:
      //    about  -- object with gadget description
      // Uses:
      //    .fetch()
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var userjs  =  PREGO.fetch( about.script ),
          i, k, n, o, p, v;
      if ( userjs ) {
         n  =  about.opts.length;
         for ( i = 0;  i < n;  i++ ) {
            o  =  about.opts[ i ];
            v  =  userjs[ o.signature ];
            if ( v !== undefined ) {
               switch ( o.type ) {
                  case "checkbox" :
                  case "radio" :
                  case "text" :
                     o.val  =  v;
                     break;
                  case "multi" :
                     if ( typeof o.poly  ===  "object"   &&   o.poly   &&
                          typeof v  ===  "object"   &&   v ) {
                        for ( k = 0;  k < p.length;  k++ ) {
                           p[ k ].val  =  v[ k ];
                        }   // for k
                     }
                     break;
               }   // switch .type
            }
         }   // for i
      }
   };   // .dialog.fresh()



   PREGO.dialog.further  =  function ( about ) {
      // Equip item with control for options
      // Precondition:
      //    about  -- object with gadget description
      //              >  .$item
      //              >  .opts
      //              >  .script
      //              >< .$button
      // Postcondition:
      //    about.$button has been set and added
      // Uses:
      //    >  .page.$btnOpts
      //    >  .type
      //    >  window.location.hash
      //    .page.factory()
      //    .dialog.fired()
      // 2019-02-13 PerfektesChaos@de.wikipedia
      if ( about.$item  &&  about.opts  &&  ! about.$button ) {
         if ( ! PREGO.page.$btnOpts ) {
            PREGO.page.factory( "$btnOpts" );
         }
         about.$button  =  PREGO.page.$btnOpts.clone();
         about.$button.attr( "id",
                             PREGO.type + "-opt_" + about.script );
         about.$button.click( function () {
                                 PREGO.dialog.fired( about );
                              } );
         if ( window.location.hash  ===  "#" + about.script ) {
            window.location.hash  =  "";
            this.fired( about );
         }
         about.$item.append( about.$button );
      }
   };   // .dialog.further()



   PREGO.hook.fetch  =  function ( assigned, action, assume ) {
      // Request for a "Gadgets" object stored by .form() or .forward()
      // Precondition:
      //    assigned  -- string with gadget identification
      //    action    -- callback function
      //    assume    -- fallback value object, if undefined or failure
      // Postcondition:
      //    Fires object, could be null
      // Uses:
      //    PREGO.fetch()
      // 2018-05-02 PerfektesChaos@de.wikipedia
      if ( typeof action  ===  "function" ) {
         action( PREGO.fetch( assigned, assume ) );
      }
   };   // .hook.fetch()



   PREGO.hook.fire  =  function () {
      // Start hook listeners
      // Uses:
      //    >  .type
      //    mw.hook()
      //    (.hook.fetch)
      //    (.form)
      //    (.forward)
      //    (.hook.$button)
      // 2018-05-02 PerfektesChaos@de.wikipedia
      mw.hook( PREGO.type + ".fetch" ).add( PREGO.hook.fetch );
      mw.hook( PREGO.type + ".form" ).add( PREGO.form );
      mw.hook( PREGO.type + ".forward" ).add( PREGO.forward );
      mw.hook( PREGO.type + ".$button" ).add( PREGO.hook.$button );
   };   // .hook.fire()



   PREGO.hook.$button =  function ( action, assign ) {
      // Request for a [options button] design
      // Precondition:
      //    action  -- callback function
      //    assign  -- optional string with gadget name
      // Postcondition:
      //    Fires jQuery object
      // Uses:
      //    PREGO.$button()
      // 2018-05-02 PerfektesChaos@de.wikipedia
      if ( typeof action  ===  "function" ) {
         action( PREGO.$button( assign ) );
      }
   };   // .hook.$button()



   PREGO.lang.factory  =  function () {
      // Create translation sequence, if not yet available
      // Uses:
      //    this
      //    >  .lang.user
      //    >< .lang.polyglott
      //     < .lang.multi
      //    mw.config.get()
      //    jQuery.inArray()
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var i, s, u;
      if ( typeof this.polyglott  !==  "object" ) {
         s  =  mw.config.get( "wgUserLanguage" ).toLowerCase();
         this.polyglott  =  [ s ];
         if ( s.length > 4 ) {   // Remove RFC 1766 subtag from code
            if ( s.charCodeAt( 2 )  ===  45 ) {   // '-'
               this.polyglott.push(  s.substr( 0, 2 )  );
            }
         }
         if ( typeof this.user[ s ]  ===  "object" ) {
            u  =  this.user[ s ];
            for ( i = 0;  i < u.length;  i++ ) {
               s  =  u[ i ];
               if ( $.inArray( s, this.polyglott )  <  0 ) {
                  this.polyglott.push( s );
               }
            }   // for i
         }
         if ( $.inArray( "en", this.polyglott )  <  0 ) {
            this.polyglott.push( "en" );
         }
         this.multi  =  this.polyglott.length;
      }
   };   // .lang.factory()



   PREGO.lang.fetch  =  function ( apply ) {
      // Retrieve string from definition, may be translated
      // Precondition:
      //    apply  -- string or object
      // Postcondition:
      //    Returns string
      // Uses:
      //    this
      //    >  .lang.multi
      //    >  .lang.polyglott
      //    .lang.factory()
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var r  =  "??????????????",
          i, s;
      switch ( typeof apply ) {
         case "string" :
            r  =  apply;
            break;
         case "object" :
            if ( apply ) {
               this.factory();
               for ( i = 0;  i < this.multi;  i++ ) {
                  s  =  this.polyglott[ i ];
                  if ( typeof apply[ s ]  ===  "string" ) {
                     r  =  apply[ s ];
                     break;   // for i
                  }
               }   // for i
            }
            break;
         case "undefined" :
            break;
         default :
            r  =  apply + "";
      }   // switch typeof apply
      return r;
   };   // .lang.fetch()



   PREGO.option.factory  =  function ( assigned, access, $append ) {
      // Create one option entry in form
      // Precondition:
      //    assigned  -- string with gadget selector
      //    access    -- object with option description
      //    $append   -- jQuery element to append to
      // Uses:
      //    this
      //    .option.flag()
      //    .option.faculty()
      //    .option.flipper()
      //    .option.field()
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var $div, $opt;
      if ( access.signature ) {
         access.selector  =  assigned + "_" + access.signature;
         $opt             =  $( "<div>" );
         $opt.attr( "id", access.selector );
         switch ( access.type ) {
            case "html" :
               $opt.append( $( access.source ) );
               break;
            case "checkbox" :
               this.flag( access, $opt );
               break;
            case "radio" :
            case "multi" :
               this.faculty( access, $opt );
               break;
            case "select" :
               this.flipper( access, $opt );
               break;
            case "text" :
               this.field( access, $opt );
               break;
            default :
               $div  =  $( "<div>" );
               $div.attr( "class", "error" );
               $div.text( "Invalid type: " + access.type );
               $opt.append( $div );
         }   // switch .type
         $append.append( $opt );
      } else {
         $div  =  $( "<div>" );
         $div.attr( "class", "error" );
         $div.text( "Invalid ID" );
         $append.append( $div );
      }
   };   // .option.factory()



   PREGO.option.faculty  =  function ( access, $append ) {
      // Create multiple item option control, radio or checkboxes
      // Precondition:
      //    access   -- object with option description
      //    $append  -- jQuery element to append to
      // Uses:
      //    >  .page.ltr
      //    .lang.fetch()
      // 2018-05-15 PerfektesChaos@de.wikipedia
      var lone    =  ( access.type === "radio" ),
          sel     =  access.selector + "_multiple",
          $div    =  $( "<div>" ),
          $label  =  $( "<label>" ),
          $sub    =  $( "<div>" ),
          live, i, k, n, o, v, $group;
      if ( typeof access.show  ===  "undefined" ) {
         $sub.attr( "class", "error" );
         $sub.text( access.selector );
      } else {
         $sub.text(  PREGO.lang.fetch( access.show )  );
      }
      $label.attr( "for", sel )
            .append( $sub );
      $div.append( $label );
      if ( typeof access.poly  ===  "object" ) {
         n  =  access.poly.length;
         if ( n ) {
            $group  =  $( "<div>" );
            $group.css( "margin-"
                                +  ( PREGO.page.ltr ? "left" : "right" ),
                        "3em" );
            if ( lone   ||   typeof access.val  ===  "object" ) {
               v  =  access.val;
            } else {
               v  =  [ access.val ];
            }
            for ( i = 0;  i < n;  i++ ) {
               o  =  access.poly[ i ];
               if ( typeof o  ===  "object"  &&  o ) {
                  sel      =  access.selector + "_" + i;
                  o.$item  =  $( "<input>" );
                  o.$item.attr( { type: access.type,
                                  id:   sel,
                                  name: access.selector } );
                  if ( lone ) {
                     live  =  ( o.val === v );
                  } else {
                     live  =  false;
                     for ( k = 0;  k < v.length;  k++ ) {
                        if ( o.val === v[ k ] ) {
                           live  =  true;
                           break;   // for k
                        }
                     }   // for k
                  }
                  if ( live ) {
                     o.$item.prop( "checked", true );
                  }
                  $group.append( o.$item );
                  $sub  =  $( "<span>" );
                  if ( typeof o.show  ===  "undefined" ) {
                     $sub.attr( "class", "error" )
                         .text( ".poly[" + i + "].show missing" );
                  } else {
                     $sub.text(  PREGO.lang.fetch( o.show )  );
                  }
                  $label  =  $( "<label>" );
                  $label.attr( "for", sel )
                        .append( $sub )
                        .css( "margin-"
                                +  ( PREGO.page.ltr ? "left" : "right" ),
                              ".5em" )
                        .append( $( "<br />" ) );
               } else {
                  $label  =  $( "<span>" );
                  $label.attr( "class", "error" )
                        .text( ".poly[" + i + "] is not an object" );
               }
               $group.append( $label );
            }   // for i
            $div.append( $group );
         } else {
            $sub  =  $( "<div>" );
            $sub.attr( "class", "error" )
                .text( "empty .poly" );
            $div.append( $sub );
         }
      } else {
         $sub  =  $( "<div>" );
         $sub.attr( "class", "error" )
             .text( "missing/invalid .poly" );
         $div.append( $sub );
      }
      $append.append( $div );
   };   // .option.faculty()



   PREGO.option.fetch  =  function ( access ) {
      // Retrieve current values in multi-control/multi-value option
      // Precondition:
      //    access   -- object with option description
      // Postcondition:
      //    Returns single or multi-select value (Array)
      // Uses:
      //    >  .type
      // 2014-02-20 PerfektesChaos@de.wikipedia
      var i, lone, n, o, swing,
          r;   //  r  =  undefined
      if ( typeof access.poly  ===  "object" ) {
         n  =  access.poly.length;
         if ( n ) {
            switch ( access.type ) {
               case "radio" :
                  lone  =  true;   // fall through
               case "multi" :
                  swing  =  "checked";
                  break;
               case "select" :
                  lone   =  access.lone;
                  swing  =  "selected";
                  break;
               default :
                  mw.log( {loud:true},
                          PREGO.type
                          + " *** Error .option.fetch() " + access.type,
                          3,
                          access );
            }   // switch access.type
            if ( ! lone ) {
               r  =  [ ];
            }
            for ( i = 0;  i < n;  i++ ) {
               o  =  access.poly[ i ];
               if ( o   &&   typeof o  === "object" ) {
                  if ( o.$item ) {
                     if ( o.$item.prop( swing ) ) {
                        if ( lone ) {
                           r  =  o.val;
                           break;   // for i
                        } else {
                           r.push( o.val );
                        }
                     }
                  }
               }
            }   // for i
         }
      }
      return r;
   };   // .option.fetch()



   PREGO.option.field  =  function ( access, $append ) {
      // Create text field control
      // Precondition:
      //    access   -- object with option description
      //    $append  -- jQuery element to append to
      // Uses:
      //    .lang.fetch()
      //    .page.fiat()
      // 2017-05-15 PerfektesChaos@de.wikipedia
      var sel     =  access.selector + "_text",
          $div    =  $( "<div>" ),
          shift, style, $label, $span;
      access.$text  =  $( "<input>" );
      access.$text.addClass( "mw-ui-input" )
                  .attr( { id:   sel,
                           type: "text" } )
                  .css( { "width": "auto" } );
      access.backup  =  ( access.val  ?  $.trim( access.val )
                                      :  "" );
      if ( access.backup ) {
         access.$text.val( access.backup );
      }
      if ( ! access.ime ) {
         access.$text.addClass( "noime" );
      }
      if ( typeof access.minimum  ===  "number" ) {
         if ( access.minimum > 0 ) {
            access.$text.attr( "size", access.minimum );
         }
      }
      if ( typeof access.maxlength  ===  "number" ) {
         if ( access.maxlength > 0 ) {
            access.$text.attr( "maxlength", access.maxlength );
         }
      }
      if ( typeof access.field  ===  "function" ) {
         access.field.call( access, access.$text );
      }
      if ( typeof access.show  !==  "undefined" ) {
         $span  =  PREGO.page.fiat(  PREGO.lang.fetch( access.show )  );
         style  =  "margin-"  +  ( PREGO.page.ltr ? "right" : "left" );
         shift  =  $span.css( style );
         if ( typeof shift  ===  "string" ) {
            if ( /^0*[a-z]*$/.test( shift ) ) {
               shift  =  false;
            }
         }
         if ( ! shift ) {
            $span.css( style, "1em" );
         }
         $label  =  $( "<label>" );
         $label.attr( "for", sel )
               .append( $span );
         $div.append( $label );
      }
      $div.append( access.$text );
      if ( typeof access.suffix  !==  "undefined" ) {
         $span  =  PREGO.page.fiat( PREGO.lang.fetch( access.suffix ) );
         style  =  "margin-"  +  ( PREGO.page.ltr ? "left" : "right" );
         shift  =  $span.css( style );
         if ( typeof shift  ===  "string" ) {
            if ( /^0*[a-z]*$/.test( shift ) ) {
               shift  =  false;
            }
         }
         if ( ! shift ) {
            $span.css( style, "1em" );
         }
         $label  =  $( "<label>" );
         $label.attr( "for", sel )
               .append( $span );
         $div.append( $label );
     }
      $append.append( $div );
   };   // .option.field()



   PREGO.option.find  =  function ( about, ask ) {
      // Retrieve option object by identification
      // Precondition:
      //    about  -- object with gadget description
      //    ask    -- string with option identification
      // Postcondition:
      //    Returns option object if found, else false
      // 2013-09-16 PerfektesChaos@de.wikipedia
      var r  =  false,
          i, n, o;
      if ( about.opts ) {
         n  =  about.opts.length;
         for ( i = 0;  i < n;  i++ ) {
            o  =  about.opts[ i ];
            if ( o  &&  o.script === ask ) {
               r  =  o;
               break;   // for i
            }
         }   // for i
      }
      return r;
   };   // .option.find()



   PREGO.option.flag  =  function ( access, $append ) {
      // Create binary checkbox option control
      // Precondition:
      //    access   -- object with option description
      //    $append  -- jQuery element to append to
      // Uses:
      //    >  .page.ltr
      //    .lang.fetch()
      // 2018-05-15 PerfektesChaos@de.wikipedia
      var sel     =  access.selector + "_checkbox",
          $label  =  $( "<label>" );
      access.$checkbox  =  $( "<input>" );
      access.$checkbox.attr( { id:   sel,
                               type: "checkbox" } );
      access.backup  =  ( access.val ? true : false );
      if ( access.backup ) {
         access.$checkbox.prop( "checked", true );
      }
      $append.append( access.$checkbox );
      $label.attr( "for", sel )
            .css( "margin-" + ( PREGO.page.ltr ? "left" : "right" ),
                  ".5em" );
      if ( typeof access.show  ===  "undefined" ) {
         $label.text( access.signature );
      } else {
         $label.text(  PREGO.lang.fetch( access.show )  );
      }
      $append.append( $label );
   };   // .option.flag()



   PREGO.option.flipper  =  function ( access, $append ) {
      // Create dropdown selction control, multiple or not
      // Precondition:
      //    access   -- object with option description
      //    $append  -- jQuery element to append to
      // Uses:
      //    >  .page.ltr
      //    PREGO.lang.fetch()
      // 2017-02-08 PerfektesChaos@de.wikipedia
      var errs    =  false,
          sel     =  access.selector + "_poly",
          $div    =  $( "<div>" ),
          $label  =  $( "<label>" ),
          $sub    =  $( "<div>" ),
          live, i, k, n, o, s, v, $select;
      if ( typeof access.show  ===  "undefined" ) {
         $sub.attr( "class", "error" )
             .text( access.selector );
      } else {
         $sub.text(  PREGO.lang.fetch( access.show )  );
      }
      $label.attr( "for", sel )
            .append( $sub );
      $div.append( $label );
      if ( typeof access.poly  ===  "object" ) {
         n  =  access.poly.length;
         if ( n ) {
            $select  =  $( "<select>" );
            $select.attr( "id", sel )
                   .css( "margin-"
                                +  ( PREGO.page.ltr ? "left" : "right" ),
                         "3em" );
            if ( typeof access.max  ===  "number"   &&   access.max ) {
               $select.attr( "size", access.max );
            }
            if ( access.lone ) {
               v  =  access.val;
            } else {
               $select.attr( "multiple", true );
               if ( typeof access.val  ===  "object" ) {
                  v  =  access.val;
               } else {
                  v  =  [ access.val ];
               }
            }
            for ( i = 0;  i < n;  i++ ) {
               o  =  access.poly[ i ];
               if ( typeof o  ===  "object" ) {
                  o.$item  =  $( "<option>" );
                  if ( access.lone ) {
                     live  =  ( o.val === v );
                  } else {
                     live  =  false;
                     for ( k = 0;  k < v.length;  k++ ) {
                        if ( o.val === v[ k ] ) {
                           live  =  true;
                           break;
                        }
                     }   // for k
                  }
                  if ( live ) {
                     o.$item.prop( "selected", true );
                  }
                  if ( typeof o.show  ===  "undefined" ) {
                     s  =  o.val + "";
                  } else {
                     s  =  PREGO.lang.fetch( o.show );
                  }
                  o.$item.text( s );
                  $select.append( o.$item );
               } else if ( errs ) {
                  errs.push( i );
               } else {
                  errs  =  [ i ];
               }
            }   // for i
            $div.append( $select );
            if ( errs ) {
               $sub  =  $( "<div>" );
               $sub.attr( "class", "error" );
               n  =  errs.length;
               s  =  ".poly["  +  ( n > 1  ?  " "  :  "" );
               for ( i = 0;  i < n;  i++ ) {
                  if ( i > 1 ) {
                     s  =  s + ", ";
                  }
                  s  =  s + i;
               }   // for i
               if ( n === 1 ) {
                  s  =  s + "] is not an object";
               } else {
                  s  =  s + " ] are no objects";
               }
               $sub.text( s );
               $div.append( $sub );
            }
         } else {
            $sub  =  $( "<div>" );
            $sub.attr( "class", "error" );
            $sub.text( "empty .poly" );
            $div.append( $sub );
         }
      } else {
         $sub  =  $( "<div>" );
         $sub.attr( "class", "error" );
         $sub.text( "missing/invalid .poly" );
         $div.append( $sub );
      }
      $append.append( $div );
   };   // .option.flipper()



   PREGO.page.factory  =  function ( acquire ) {
      // Ensure initialization on the fly. Generate GUI elements.
      // Precondition:
      //    acquire  -- request
      //                false           -- general
      //                "Blankpage"
      //                "Gadgets"
      //                "$btnClose",
      //                "$btnOpts",
      //                "$btnSubmit"    -- button
      //                "$optsForm"     -- wrap for any options form
      // Uses:
      //    this
      //    >  .config
      //    >  .type
      //    >  Version
      //    >  mw.util.$content
      //    >< .page.ltr
      //    >< .page.gallery
      //    >< .page.$btnClose
      //    >< .page.$btnOpts
      //    >< .page.$btnSubmit
      //    >< .page.$optsForm
      //    >< .page.$sectionUser
      //    >< .page.$throbber
      //    >< .page.$stored
      //    >< .page.$denied
      //    .page.fancy()
      //    mw.config.get()
      // 2018-05-15 PerfektesChaos@de.wikipedia
      var i, permit, q, s, $e, $v;
      if ( ! this.gallery ) {
         this.gallery  = {
            "$btnOpts" :  [ "Nuvola_apps_kcmsystem.svg",
                            "7a", true, true ],
            "$throbber" : [ "Ajax-loader.gif",
                            "de", false, false, "API..." ],
            "$stored" :   [ "Symbol_OK.png",
                            "62", true, false, "Okay" ],
            "$denied" :   [ "Boos.png",
                            "38", true, false, ":-(" ]
         };
      }
      switch ( acquire ) {
         case false :
            if ( typeof this.ltr  !==  "boolean" ) {
               //    PREGO.page.find();  Special:Gadgets only
               if ( typeof PREGO.config  ===  "object"
                    &&     PREGO.config ) {
                  permit  =  [ "$btnClose",
                               "$btnOpts",
                               "$btnSubmit",
                               "$optsForm",
                               "$sectionUser" ];
                  for ( i = 0;  i < permit.length;  i++ ) {
                     s   =  permit[ i ];
                     $e  =  PREGO.config[ s ];
                     if ( $e
                          &&   typeof $e  ===  "object"
                          &&   typeof $e.clone  ===  "function" ) {
                        this[ s ]  =  PREGO.config[ s ].clone();
                     }
                  }   // for i
               }
               this.ltr  =  ( $( window.document ).find( "html" )
                                                  .attr( "dir" )
                              !==  "rtl" );
            }
            break;
         case "Blankpage" :
            q   =  mw.config.get( "wgFormattedNamespaces" );
            $v  =  $( "#firstHeading,#section_0" );
            if ( ! $v.length ) {
               $v = $( "h1" );
            }
            $v.eq( 0 ).text( q[ "2" ] + ": " + q[ "2300" ] )
                      .attr( "title", Version );
            $v  =  mw.util.$content.find( "#mw-content-text" );
            $v.empty();
            $e  =  $( "<div>" );
            $e.attr( { "id":  PREGO.type + "-user" } );
            $v.prepend( $e );
            this.$sectionUser  =  $e;
            $v  =  $( "head" );
            $e  =  $v.find( "title" );
            $e.remove();
            $e  =  $( "<title>" );
            $e.text( PREGO.type );
            $v.prepend( $e );
            break;
/* 2016-10 unreachable
         case "Gadgets" :
            $e  =  $( "<div>" );
            $e.attr( "id",  PREGO.type + "-user" )
              .css( { "border":  "solid 2px #80FFFF",
                      "margin":  "1em",
                      "padding": "1em" } );
            $v  =  $( "<h2>" );
            s   =  mw.config.get( "wgFormattedNamespaces" );
            $v.text( s[ "2" ] );
            $e.append( $v );
            mw.util.$content.append( $e );
            this.$sectionUser  =  $e;
            break;
*/
         case "$btnClose" :
         case "$btnOpts" :
         case "$btnSubmit" :
            if ( ! this[ acquire ] ) {
               $e  =  $( "<button>" );
               $e.attr( "type", "button" );
               switch ( acquire ) {
                  case "$btnClose" :
                     $e.css( { "background-color": "#FF0000",
                               "color":            "#FFFFFF",
                               "font-size":        "8px",
                               "font-weight":      "bolder",
                               "float":            ( this.ltr ? "right" :
                                                                "left" ),
                               "height":           "16px",
                               "padding":          "0",
                               "text-align":       "center",
                               "vertical-align":   "middle",
                               "width":            "16px"
                             } );
                     $v  =  $( "<span>" );
                     $v.text( "X" );
                     break;
                  case "$btnOpts" :
                     $e.css( { "background-color": "#80FFFF",
                               "margin-bottom":    ".5em" } );
                     $e.css( "margin-"  +  ( this.ltr ? "left"
                                                      : "right" ),
                             "1em" );
                     $e.attr( "title", "Option..." );
                     $v  =  this.fancy( acquire, 20 );
                     break;
                  case "$btnSubmit" :
                     $e.css( { "color":       "#00A000",
                               "font-size":   "200%",
                               "font-weight": "bolder",
                               "margin-top":  "5px" } );
                     $e.attr( "title", "...API...>>" );
                     $v  =  $( "<span>" );
                     $v.text( "+" );
                     break;
               }   // switch acquire
               $e.append( $v );
               this[ acquire ]  =  $e;
            }
            break;
         case "$optsForm" :
            if ( ! this.$optsForm ) {
               $e  =  $( "<div>" );
               $e.attr( "class",  PREGO.type + "-optsForm" )
                 .css( { "border":  "solid 1px #80FFFF",
                         "margin":  ".5em",
                         "padding": ".5em" } );
               this.$optsForm  =  $e;
            }
            break;
         case "$throbber" :
         case "$stored" :
         case "$denied" :
            if ( ! this[ acquire ] ) {
               this[ acquire ]  =  this.fancy( acquire, 18 );
            }
            break;
      }   // switch acquire
   };   // .page.factory()



   PREGO.page.fancy  =  function ( access, amount ) {
      // Create thumb image
      // Precondition:
      //    access  -- image identifier
      //    amount  -- number of pixels
      // Postcondition:
      //    Returns jQuery object <img>
      //    this
      //    >  .page.gallery
      // 2013-12-26 PerfektesChaos@de.wikipedia
      var src      =  "//upload.wikimedia.org/wikipedia/commons/",
          picture  =  this.gallery[ access ],
          s, $r;
      if ( picture ) {
         if ( picture[ 2 ] ) {
            src  =  src + "thumb/";
         }
         s    =  picture[ 1 ];
         src  =  src  +  s.substr(0, 1)  +  "/"  +  s  +  "/"
                 + picture[ 0 ];
         if ( picture[ 2 ] ) {
            src  =  src + "/" + amount + "px-" + picture[ 0 ];
            if ( picture[ 3 ] ) {
               src  =  src + ".png";
            }
         }
         $r  =  $( "<img />" );
         $r.attr( "src", src );
         if ( picture[ 4 ] ) {
            $r.attr( "alt", picture[ 4 ] );
         }
      } else {
         $r  =  $( "<span>" );
         $r.text( "???" + access + "???" );
      }
      return $r;
   };   // .page.fancy()



   PREGO.page.feature  =  function ( about ) {
      // Create user defined gadget item on page
      // Precondition:
      //    about  -- object with gadget description
      // Postcondition:
      //    about.$item has been set
      // Uses:
      //    this
      //    >  .page.$sectionUser
      //    >  .page.$list
      //    >  .type
      //    .lang.fetch()
      //    .page.first()
      // 2021-10-08 PerfektesChaos@de.wikipedia
      var e, i, n, p, say, $a, $span, $span2;
      if ( typeof this.$sectionUser  ===  "undefined" ) {
         this.first();
      }
      if ( typeof this.$list  ===  "object" ) {
         if ( typeof about.show  ===  "undefined" ) {
            say  =  about.script;
         } else {
            say  =  PREGO.lang.fetch( about.show );
         }
         $span   =  $( "<span>" );
         $span2  =  $( "<span>" );
         $span2.html( say );
         if ( typeof about.support  ===  "undefined" ) {
            $span.append( $span2 );
         } else {
            $a  =  $( "<a>" );
            $a.attr( { href:   PREGO.lang.fetch( about.support ),
                       target: "_blank" } )
              .append( $span2 );
            $span.append( $a );
         }
         if ( typeof about.suffix  !==  "undefined" ) {
            $span2  =  $( "<span>" );
            $span2.html( PREGO.lang.fetch( about.suffix ) );
            $span.append( "&#160;" );
            $span.append( $span2 );
         }
         about.$item  =  $( "<li>" );
         about.$item.attr( "id",  about.script );
         about.$item.append( $span );
         say  =  say.toLowerCase().replace( /-_.,;/,  " " );
         p    =  [ say, about.$item ];
         if ( this.gadgets ) {
            n  =  this.gadgets.length;
            for ( i = 0;  i < n;  i++ ) {
               e  =  this.gadgets[ i ];
               if ( say  <  e[ 0 ] ) {
                  e[ 1 ].before( about.$item );
                  break;   // for i
               }
            }   // for i
            e  =  [ say, about.$item ];
            if ( i === n ) {
               this.$list.append( about.$item );
               this.gadgets.push( p );
            } else {
               this.gadgets.splice( i, 0, p );
            }
         } else {
            this.$list.append( about.$item );
            this.gadgets  =  [ p ];
         }
      } else {
         about.$item  =  true;
      }
   };   // .page.feature()



   PREGO.page.features  =  function () {
      // Insert one or more gadget items on page
      // Precondition:
      //    preferences page
      // Uses:
      //    >  .page.queue
      //    >< .page.launched
      //    .page.factory()
      //    .page.feature()
      //    .dialog.further()
      // Remark: Used as event handler -- 'this' is not PREGO.page
      // 2019-02-13 PerfektesChaos@de.wikipedia
      var gadget, i, n;
      if ( ! PREGO.page.launched ) {
         PREGO.page.launched  =  true;
         PREGO.page.factory( false );
      }
      n  =  PREGO.page.queue.length;
      for ( i = 0;  i < n;  i++ ) {
         gadget  =  PREGO.page.queue[ i ];
         if ( gadget   &&   typeof gadget  ===  "object" ) {
            if ( typeof gadget.script  ===  "string" ) {
               PREGO.page.queue[ i ]  =  gadget.script;
            } else {
               PREGO.page.queue[ i ]  =  i;
            }
            if ( typeof gadget.$item  !==  "object" ) {
               PREGO.page.feature( gadget );
               if ( gadget.$item   &&   gadget.$item !== true
                    &&     gadget.opts   &&
                    typeof gadget.opts  ===  "object" ) {
                  PREGO.dialog.further( gadget );
               }
            }
         }
      }   // for i
   };   // .page.features()



   PREGO.page.fiat  =  function ( adjust ) {
      // Create text field control
      // Precondition:
      //    adjust  -- string with possible HTML
      // Postcondition:
      //    Returns jQuery object
      // 2013-10-06 PerfektesChaos@de.wikipedia
      var $r;
      if ( adjust.charCodeAt(0) === 60 ) {   // '<'
         $r  =  $( adjust );
      } else {
         $r  =  $( "<span>" );
         $r.text( adjust );
      }
      return $r;
   };   // .page.fiat()



/* 2016-10 unreachable page
   PREGO.page.find  =  function () {
      // Initialize "Gadgets" page server definition items
      // Precondition:
      //    "Gadgets" page ready
      // Uses:
      //    this
      //    >  mw.util.$content
      //     < .page.items
      // 2016-10-08 PerfektesChaos@de.wikipedia
      var $items  =  mw.util.$content.find( "li" ),
          n       =  $items.length,
          i, k, s, $li, $links;
      this.items  =  { };
      for ( i = 0;  i < n;  i++ ) {
         $li     =  $items.eq( i );
         $links  =  $li.find( "a" );
         for ( k = 0;  k < $links.length;  k++ ) {
            s  =  $links.eq( k ).attr( "title" );
            if ( s ) {
               if ( s.indexOf( "MediaWiki:Gadget-" ) === 0 ) {
                  this.items[ s.substr( 17 ) ]  =  $li;
                  break;   // for k
               }
            }
         }   // for k
      }   // for i
   };   // .page.find()
*/



   PREGO.page.first  =  function () {
      // Initialize page and page section
      // Precondition:
      //    Special page ready
      // Uses:
      //    this
      //    >  .special
      //    >  .page.$sectionUser
      //    >  .type
      //    <  .page.$list
      //    .page.factory()
      // 2016-10-08 PerfektesChaos@de.wikipedia
      this.factory( PREGO.special );
      if ( typeof this.$sectionUser  ===  "object" ) {
         this.$list  =  $( "<ul />" );
         this.$list.attr( "id",  PREGO.type + "-list" );
         this.$sectionUser.append( this.$list );
      }
   };   // .page.first()



   PREGO.page.fragment  =  function ( assign ) {
      // Create identifier for gadget storage submission buttons
      // Precondition:
      //    assign  -- string of particular gadget, or nothing
      // Postcondition:
      //    Returns string with identifier
      // Uses:
      //    >  .type
      // 2015-09-25 PerfektesChaos@de.wikipedia
      var r  =  PREGO.type + "-SUBMIT";
      if ( assign ) {
         r  =  r + "_" + assign;
      }
      return r;
   };   // .page.fragment()



   PREGO.factory  =  function ( apply, adjacent ) {
      // Encode/decode any value
      // Precondition:
      //    apply     -- value, of any type;  or encoded string
      //    adjacent  -- value type for storing, false for retrieving
      // Postcondition:
      //    Returns string for storing, or retrieved value
      // Uses:
      //    parseInt()
      //    JSON.stringify()
      //    .date.format8601()
      //    .date.from8601()
      //    JSON.parse()
      // 2014-10-01 PerfektesChaos@de.wikipedia
      var r;
      if ( adjacent ) {    // storing
         r  =  adjacent + "|";
         switch ( r ) {
            case "boolean|" :
               if ( apply ) {
                  r  =  "boolean|true";
               }
               break;
            case "Boolean|" :
               if ( apply.valueOf() ) {
                  r  =  "boolean|true";
               }
               break;
            case "number|" :
            case "Number|" :
            case "string|" :
            case "String|" :
               r  =  r.toLowerCase() + apply;
               break;
            case "object|" :
               if ( r ) {
                  r  =  r  +  JSON.stringify( apply );
               } else {
                  r  =  "null|";
               }
               break;
            case "date|" :
            case "Date|" :
               r  =  "date|" + PREGO.date.format8601( apply );
               break;
            default :
               r  =  "undefined|";
         }   // switch typeof apply
      } else if ( typeof apply  ===  "string" ) {    // retrieving
         r  =  apply.indexOf( "|" );
         if ( r > 0 ) {
            switch ( apply.substr( 0, r ) ) {
               case "boolean" :
                  r  =  ( apply === "boolean|true" );
                  break;
               case "number" :
                  r  =  parseInt( apply.substr( r + 1 ),  10 );
                  break;
               case "string" :
                  r  =  apply.substr( r + 1 );
                  break;
               case "null" :
                  r  =  null;
                  break;
               case "object" :
                  r  =  JSON.parse( apply.substr( r + 1 ) );
                  break;
               case "date" :
                  r  =  PREGO.date.from8601( apply.substr( r + 1 ) );
                  break;
               case "undefined" :
                  r  =  undefined;
                  break;
               default :
                  r  =  apply;
            }   // switch typeID
         } else {
            r  =  apply;
         }
      } else {
         r  =  undefined;
      }
      return r;
   };   // .factory()



   PREGO.fetch  =  function ( assign, assume, again, aborted ) {
      // Retrieve a "Gadgets" object stored by .form() or .forward()
      // Precondition:
      //    assign   -- string with gadget identification
      //    assume   -- fallback value object, if undefined or failure
      //                kept unchanged; also default for single values
      //    again    -- update value from server (optional)
      //                callback function, or true
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns object, could be null
      // Uses:
      //    this
      //    >  .local
      //    .api.fresh()
      //    mw.user.options.get()
      //    JSON.parse()
      // 2015-09-12 PerfektesChaos@de.wikipedia
      var o, r, s;
      if ( typeof assign  ===  "string" ) {
         if ( again  &&  ! this.local ) {
            this.api.fresh( assign, 2, assume, again, aborted );
         }
         r  =  mw.user.options.get( "userjs-" + assign );
         if ( r ) {
            if ( typeof r  ===  "string" ) {
               o  =  JSON.parse( r );
               if ( assume   &&   typeof assume  ===  "object" )  {
                  r  =  { };
                  for ( s in assume )  {
                     r[ s ]  =  assume[ s ];
                  }    // for s in assume
                  for ( s in o )  {
                     r[ s ]  =  o[ s ];
                  }    // for s in o
               } else {
                  r  =  o;
               }
            }
         } else if ( r === undefined )  {
            r  =  ( assume ? assume : null );
         }
      } else {
         r  =  ( assume ? assume : null );
      }
      return r;
   };   // .fetch()



   PREGO.finalize  =  function () {
      // Ready for use, communicate
      // Uses:
      //    this
      //    >  .type
      //    mw.hook()
      // 2016-03-18 PerfektesChaos@de.wikipedia
      mw.hook( this.type + ".ready" ).fire( this );
   };   // .finalize()



   PREGO.form  =  function ( about ) {
      // Show gadget (entry, and dialog) on "Gadgets" page
      // Precondition:
      //    about  -- object with gadget description, or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    >  .special
      //    >  .type
      //    >< .page.queue
      //    >< .page.launched
      //    mw.config.get()
      //    .page.features()
      //    mw.loader.load()
      //    mw.hook()
      //    (.page.features)
      // 2019-02-13 PerfektesChaos@de.wikipedia
      var r  =  false,
          s  =  mw.config.get( "wgCanonicalSpecialPageName" );
      if ( s === PREGO.special ) {
         if ( s === "Blankpage" ) {
            s  =  mw.config.get( "wgTitle" );
            r  =  ( s.indexOf( "/" + PREGO.type,  3 )   >   0 );
         }
         if ( r   &&   typeof about  ===  "object" ) {
            if ( typeof PREGO.page.queue  ===  "object" ) {
               PREGO.page.queue.push( about );
               if ( PREGO.page.launched ) {
                  PREGO.page.features();
               }
            } else {
               PREGO.page.queue     =  [ about ];
               PREGO.page.launched  =  false;
               mw.loader.load( [ "mediawiki.ui.input" ] );
               mw.hook( "wikipage.content" ).add( PREGO.page.features );
            }
         }
      }
      return r;
   };   // .form()



   PREGO.forward  =  function ( assign, apply, after, aborted ) {
      // Store a "Gadgets" options object
      // Precondition:
      //    assign   -- string with gadget identification
      //    apply    -- options object (not null)
      //    after    -- callback function (optional), or false
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    >  .local
      //    JSON.stringify()
      //    .cache.fill()
      //    .api.fire()
      //    mw.user.options.set()
      // 2018-05-05 PerfektesChaos@de.wikipedia
      var r, store;
      if ( typeof assign  ===  "string"   &&
           typeof apply  ===  "object"   &&   apply ) {
         store = JSON.stringify( apply );
         if ( PREGO.local ) {
            PREGO.cache.fill( assign, store );
         } else {
            PREGO.api.fire( assign,
                           store,
                           false,
                           after,
                           aborted,
                           true );
         }
         mw.user.options.set( "userjs-" + assign,  store );
         r  =  false;
      } else {
         r  =  "ERROR * mw.libs." + PREGO.type + ".forward()"
               + " * invalid arg";
      }
      return r;
   };   // .forward()



   PREGO.get  =  function ( assign, assume, again, aborted ) {
      // Retrieve a typesafe USERJS option value stored by .put()
      // Precondition:
      //    assign   -- string with option identification
      //    assume   -- fallback value, if undefined or failure
      //    again    -- update value from server
      //                callback function, or true
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns value, of any type
      // Uses:
      //    this
      //    >  .local
      //    mw.user.options.get()
      //    .api.fresh()
      //    .factory()
      // 2015-09-12 PerfektesChaos@de.wikipedia
      var r;
      if ( typeof assign  ===  "string" ) {
         r  =  mw.user.options.get( "userjs-" + assign );
         if ( this.local ) {
            if ( typeof r  !==  "string" ) {
               r  =  undefined;
            }
         } else if ( again ) {
            this.api.fresh( assign, 1, assume, again, aborted );
         }
         if ( r ) {
            r  =  this.factory( r,  false );
         }
      }
      if ( r === undefined ) {
         r  =  assume;
      }
      return r;
   };   // .get()



   PREGO.put  =  function ( assign, apply, after, aborted ) {
      // Store a typesafe USERJS option value
      // Precondition:
      //    assign   -- string with option identification
      //    apply    -- value, of any type
      //    after    -- callback function (optional), or false
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    this
      //    >  .local
      //    .factory()
      //    .cache.fill()
      //    .api.fire()
      //    mw.user.options.set()
      // 2015-09-12 PerfektesChaos@de.wikipedia
      var s = $.type( apply ),
          r;
      if ( ! apply  &&  s === "object" ) {
         s  =  "null";
      }
      s  =  this.factory( apply, s );
      if ( this.local ) {
         this.cache.fill( assign, s );
         r  =  false;
      } else {
         r  =  this.api.fire( assign, s, false, after, aborted );
         if ( r  &&  s === "object" ) {
            r  =  false;
         }
      }
      mw.user.options.set( "userjs-" + assign,  s );
      return r;
   };   // .put()



   PREGO.remove  =  function ( assign, after, aborted ) {
      // Remove a preference entry
      // Precondition:
      //    assign   -- string with option identification
      //    after    -- callback function (optional), or false
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    this
      //    >  .local
      //    .api.fire()
      //    mw.user.options.get()
      //    mw.user.options.set()
      // 2015-09-12 PerfektesChaos@de.wikipedia
      var r, s, v;
      if ( this.local ) {
         this.cache.free( assign );
      } else {
         r  =  this.api.fire( assign, null, false, after, aborted );
      }
      if ( ! r ) {
         s  =  "userjs-" + assign;
         v  =  mw.user.options.get( s );
         if ( v !== null  &&  v !== undefined ) {
            mw.user.options.set( s, null );
         }
      }
      return r;
   };   // .remove()



   PREGO.string  =  function ( assign, apply, after, aborted ) {
      // Store a string value of a MEDIAWIKI preference
      //    Note that "false" causes other behaviour than false.
      // Precondition:
      //    assign   -- string with option name
      //    apply    -- string value
      //    after    -- callback function (optional), or false
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    this
      //    >  .type
      //    .api.fire()
      //    mw.user.options.set()
      // 2013-09-19 PerfektesChaos@de.wikipedia
      var r;
      if ( typeof assign  ===  "string"   &&
           typeof apply  ===  "string" ) {
         r  =  this.api.fire( assign, apply, true, after, aborted );
         mw.user.options.set( assign, apply );
      } else {
         r  =  "ERROR * mw.libs." + PREGO.type + ".string()"
               + " * invalid arg";
      }
      return r;
   };   // .string()



   PREGO.translation  =  function ( available ) {
      // Retrieve best translation from object
      // Precondition:
      //    available   -- object with items accessible by language code
      // Postcondition:
      //    Returns component with best matching language code
      // Uses:
      //    this
      //    >  .lang.polyglott
      //    >  .lang.multi
      //    .lang.factory()
      // 2014-03-20 PerfektesChaos@de.wikipedia
      var r  =  false,
          i, slang;
      if ( typeof this.lang.polyglott  !==  "object" ) {
         this.lang.factory();
      }
      if ( typeof available  ===  "object"   &&
           available ) {
         for ( i = 0;  i < this.lang.multi;  i++ ) {
            slang  =  this.lang.polyglott[ i ];
            if ( typeof available[ slang ]  !==  "undefined" ) {
               r  =  available[ slang ];
               break;   // for i
            }
         }   // for i
      }
      return r;
   };   // .translation()



   PREGO.translator  =  function () {
      // Retrieve Array with possible language codes
      // Postcondition:
      //    Returns Array with possible language codes, sorted best first
      // Uses:
      //    this
      //    >  .lang.polyglott
      //    .lang.factory()
      // 2014-03-19 PerfektesChaos@de.wikipedia
      if ( typeof this.lang.polyglott  !==  "object" ) {
         this.lang.factory();
      }
      return this.lang.polyglott;
   };   // .translator()



   PREGO.update  =  function ( assign, after, aborted ) {
      // Update the value of a MEDIAWIKI preference
      // Precondition:
      //    assign   -- string with option name
      //    after    -- callback function after update
      //    aborted  -- error callback function (optional), or false
      // Postcondition:
      //    Returns false if valid, else error message on parameter fault
      // Uses:
      //    this
      //    >  .type
      //    mw.user.options.get()
      //    .api.fresh()
      // 2013-09-28 PerfektesChaos@de.wikipedia
      var r;
      if ( typeof assign  ===  "string" ) {
         this.api.fresh( assign,
                         0,
                         mw.user.options.get( assign ),
                         after,
                         aborted );
         r  =  false;
      } else {
         r  =  "ERROR * mw.libs." + PREGO.type + ".update()"
               + " * invalid arg";
      }
      return r;
   };   // .update()



   PREGO.$button  =  function ( assign ) {
      // Retrieve current [options button] design
      // Precondition:
      //    assign   -- optional string with gadget name
      // Postcondition:
      //    Returns jQuery object
      // Uses:
      //    >  .special
      //    >  .type
      //    >  .page.$btnOpts
      //    mw.config.get()
      //    mw.util.getUrl()
      //    .page.factory()
      // 2016-10-09 PerfektesChaos@de.wikipedia
      var shift    =  mw.config.get( "wgDBname" ) + "Gadgets",
          special  =  ( PREGO.special === "Blankpage"  ?
                        "Blankpage/" + PREGO.type      :
                        PREGO.special ),
          src      =  window.location.protocol + "//"
                      + window.location.host
                      + mw.util.getUrl( "Special:" + special ),
          $btn;
      if ( assign ) {
         src  =  src + "#" + assign;
      }
      if ( ! PREGO.page.$btnOpts ) {
         PREGO.page.factory( "$btnOpts" );
      }
      $btn  =  PREGO.page.$btnOpts.clone();
      $btn.click( function () {
                     window.open( src, shift );
                     // Returns always false, stop the bubbling
                     return false;
                  } );
      return $btn;
   };   // .$button()



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



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

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