Jump to content

User:PerfektesChaos/js/citoidWikitext/core/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/citoidWikitext/core.js
//  Execute core functionality for citoid on wikitext source editing
/// 2018-09-03 PerfektesChaos@de.wikipedia
// ResourceLoader:  compatible;
//    dependencies: --
/// 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   = -4.3,
       Signature = "citoidWikitext",
       Sub       = "core",
       Sites     = "|dewiki|",
       APIC      = { },
       BOOK      = { },
       DESC      = { },
       EDIT      = { codeMirror: false,
                     wikEd:      false },
       GUIP      = { btn:    { },
                     caret:  { },
                     gadget: { },
                     input:  { },
                     push:   { } },
       HELP      = { site:    "w:en",
                     support: "user:PerfektesChaos/js/" + Signature },
       I18N      = { },
       PREGO     = { maxage:    604812,
                     signature: "preferencesGadgetOptions",
                     site:      "w:en",
                     store:     "User:PerfektesChaos/js/",
                     sub:       "/r.js" },
       REPOS     = { maxage: 86400,
                     site:   "w:en",
                     store:  "User:PerfektesChaos/js/"  },
       PROJ      = false,
       TASK      = { },
       UTIL      = { },
       XPORT     = { core: { sites: Sites } },
       CITWT;



   function facilitated() {
      // Mark sub-module as ready
      // Uses:
      //    >  Signature
      //    >  Sub
      //    >  Version
      //    >  XPORT
      //     < CITWT
      //     < .signature
      //     < .vsn
      //     < .type
      //    mw.loader.getState()
      //    mw.loader.state()
      //    mw.hook()
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var sign      = "ext.gadget." + Signature,
          sub       = "/" + Sub,
          signature = sign + sub,
          s         = mw.loader.getState( signature ),
          e, o, rls;
      if ( s !== "loaded"  &&  s !== "ready" ) {
         rls = { };
         rls[ signature ] = "loaded";
         mw.loader.state( rls );
         if ( typeof mw.libs[ Signature ]  !==  "object"   ||
              !      mw.libs[ Signature ] ) {
            mw.libs[ Signature ] = { };
         }
         CITWT = mw.libs[ Signature ];
         CITWT.signature = sign;
         if ( typeof CITWT.vsn  ===  "string" ) {
            CITWT.vsn = CITWT.vsn + " ";
         } else {
            CITWT.vsn = "";
         }
         CITWT.vsn   = CITWT.vsn + Sub.substr( 0, 1 ) + ":" + Version;
         CITWT.type  = Signature;
         CITWT.suite = ( Version > 1  ?  "r"  :  "d" );
         if ( typeof CITWT[ Sub ]  !==  "object"   ||
              !      CITWT[ Sub ] ) {
            CITWT[ Sub ]  =  { };
         }
         e = XPORT[ Sub ];
         o = CITWT[ Sub ];
         for ( s in e ) {
            o[ s ] = e[ s ];
         }   // for s in e
         for ( s in XPORT ) {
            if ( typeof XPORT[ s ]  ===  "function" ) {
               CITWT[ s ] = XPORT[ s ];
            }
         }   // for s in XPORT
         rls[ signature ] = "ready";
         mw.loader.state( rls );
         mw.hook( CITWT.type + sub + ".ready" ).fire();
      }
   }   // facilitated()



//-----------------------------------------------------------------------



   APIC.site      = "https://citoid.wikimedia.org/api";
   APIC.stateless = "/api/rest_v1/data/citation/mediawiki/";



   APIC.fault = function ( jqXHR, textStatus, errorThrown ) {
      // API failure
      // Precondition:
      //    Common failure call
      // Uses:
      //    >  .type
      //     < APIC.scream
      //    GUIP.flat()
      //    console.log()
      //    console.dir()
      // Remark: Used as event handler -- 'this' is not APIC
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.flat( "unanswered" );
      APIC.scream = textStatus + "  -- Error: " + errorThrown;
      if ( typeof window.console  ===  "object"   &&
           typeof window.console.log  ===  "function" ) {
         window.console.log( CITWT.type + " * " + APIC.scream );
         if ( textStatus   &&
              typeof textStatus  ===  "object"   &&
              typeof window.console.dir  ===  "function" ) {
            window.console.dir( textStatus );
         }
      }
   };   // APIC.fault()



   APIC.finish = function () {
      // All queries have been answered
      // Uses:
      //    >  APIC.results
      //    >< APIC.queries
      //    TASK.follow()
      // 2017-06-10 PerfektesChaos@de.wikipedia
      var n = APIC.queries.length,
          i, j, q, r, ufo;
      for ( i = 0;  i < APIC.results.length;  i++ ) {
         r = APIC.results[ i ];
         if ( r ) {
            for ( j = 0;  j < n;  j++ ) {
               q = APIC.queries[ j ];
               if ( q === r.url ) {
                  r.lucky           = true;
                  APIC.queries[ j ] = r;
                  r                 = false;
                  break;   // for j
               }
            }   // for j
            if ( r ) {
               if ( ! ufo ) {
                  ufo = [ ];
               }
               r.idResult = i;
               ufo.push( r );
            }
         }
      }   // for i
      if ( ufo ) {
         i = 0;
         if ( ufo.length === 1 ) {
            ufo[ 0 ].idResult = false;
         }
         for ( j = 0;  j < n;  j++ ) {
            q = APIC.queries[ j ];
            if ( typeof q  ===  "string" ) {
               ufo[ i ].queryURL = q;
               APIC.queries[ j ] = ufo[ i ];
               i++;
            }
         }   // for j
      }
      TASK.follow();
   };   // APIC.finish()



   APIC.fire = function () {
      // Run queries at WMF via API
      // Precondition:
      //    mediawiki.api has been loaded
      // Uses:
      //    >  .server
      //    >  APIC.site
      //    >  APIC.stateless
      //    >  APIC.queries
      //    >< APIC.data
      //    >< APIC.jq
      //    >< APIC.legacy
      //     < APIC.results
      //     < APIC.missing
      //    (APIC.found)
      //    (APIC.fault)
      // Remark: Used as event handler -- 'this' is not APIC
      // 2017-06-11 PerfektesChaos@de.wikipedia
      var f, i, query, service;
      if ( typeof APIC.jq  !==  "object" ) {
         f = function ( jqXHR ) {
                            jqXHR.setRequestHeader( "Content-Type",
                                                    "application/json" );
             };
         if ( typeof CITWT.server  ===  "string" ) {
            service = CITWT.server;
         } else {
            service = APIC.site;
         }
         APIC.data   = { format: "mediawiki" };   // mediawiki zotero
         APIC.jq     = { beforeSend: f,
                         dataType:   "json",   // No "Intelligent Guess"
                         url:        service };
         APIC.legacy = ( service.indexOf( "/url" )  >  0 );
         if ( APIC.legacy ) {
            APIC.jq.type = "POST";
         } else if ( APIC.stateless ) {
            APIC.site = window.location.protocol + "//" +
                        window.location.hostname + APIC.stateless;
         } else {
            APIC.jq.data = APIC.data;
         }
      }
      APIC.results = [ ];
      APIC.missing = APIC.queries.length;
      for ( i = 0;  i < APIC.missing;  i++ ) {
         /* citoid.wmflabs appears to handle an Array of URL
            and will return an Array of result objects,
            but there is nothing promised nor documented
            nor a stable implementation in spring 2015.
         */
         query = APIC.queries[ i ];
         switch ( typeof query ) {
            case "string":
               if ( APIC.legacy ) {
                  APIC.data.url = query;
                  APIC.jq.data  = JSON.stringify( APIC.data );
               } else if ( APIC.stateless ) {
                  APIC.jq.url = APIC.site + encodeURIComponent( query );
               } else {
                  APIC.jq.data.search = query;
               }
               $.ajax( APIC.jq ).done( APIC.found )
                                .fail( APIC.fault );
               break;
            case "object":
               if ( typeof query.beforeSend  ===  "string"
                    &&     query.beforeSend  ===  "json" ) {
                  query.jq.beforeSend = f;
               }
               query.jq.beforeSend = f;
               $.ajax( query.jq ).done( query.found )
                                 .fail( query.fault );
               break;
         }   // switch typeof query
      }   // for i
   };   // APIC.fire()



   APIC.found = function ( arrived ) {
      // Answer arrived
      // Precondition:
      //    arrived  -- object, full result
      //                string, favorite result
      //                boolean, no favorite result
      // Uses:
      //    >< APIC.missing
      //    >< APIC.results
      //    GUIP.flat()
      //    TASK.follow()
      //    APIC.finish()
      // Remark: Used as event handler -- 'this' is not APIC
      // 2018-07-14 PerfektesChaos@de.wikipedia
      var got;
      switch ( typeof arrived ) {
         case "boolean":
            APIC.missing--;
            break;
         case "string":
            APIC.missing = -1;
            GUIP.flat();
            TASK.follow();
            break;
         case "object":
            if ( arrived   &&   typeof arrived.length  ===  "number" ) {
               got = arrived[ 0 ];
               if ( got   &&   typeof got  ===  "object" ) {
                  if ( typeof got.url  ===  "string"   &&   got.url ) {
                     APIC.results.push( got );
                     APIC.missing--;
                  } else if ( typeof got.Error  ===  "string" ) {
                     // Never reached
                     //       HTTP-OPTIONS call APIC.fault() only
                     // Plan:
                     //   * Record on console
                     //     shared with APIC.fault()
                     //   * Enrich flat("unanswered") by details
                     GUIP.flat( "unanswered" );
                  // GUIP.flat( "unanswered", $span );
                  }
               }
            }
            break;
      }   // switch typeof arrived
      if ( ! APIC.missing ) {
         APIC.finish();
      }
   };   // APIC.found()



//-----------------------------------------------------------------------



   BOOK.fire = function () {
      // Initialize ISBN support
      // Uses:
      //    mw.hook()
      //    (BOOK.first)
      // 2016-03-21 PerfektesChaos@de.wikipedia
      mw.hook( "isbnLib.ready" ).add( BOOK.first );
   };   // BOOK.fire()



   BOOK.first = function ( application ) {
      // Uses:
      // Precondition:
      //    application   -- isbnLib
      // Uses:
      //     < .isbnLib
      //    .isbnLib.furnish()
      //    GUIP.fire()
      // 2016-03-25 PerfektesChaos@de.wikipedia
      CITWT.isbnLib  =  application;
      switch ( typeof CITWT.isbn ) {
         case "string":
         case "object":
            CITWT.isbnLib.furnish( CITWT.isbn );
      }   // switch typeof CITWT.isbn
      GUIP.fire();
   };   // BOOK.first()



//-----------------------------------------------------------------------



   DESC.re = { };



   DESC.fire = function ( attempt ) {
      // Analyse request description string
      // Precondition:
      //    attempt  -- string or false
      // Postcondition:
      //    Returns object, or false
      // Uses:
      //    >  GUIP.caret.story
      //    DESC.fold()
      //    DESC.*()
      // 2015-07-01 PerfektesChaos@de.wikipedia
      var got, i, mode, r, s, start;
      if ( attempt ) {
         s = attempt.replace( /^\s+/, "" );
         i = s.indexOf( "</ref>" );
         if ( i >= 0 ) {
            s = s.substr( 0, i );
         }
         if ( s ) {
            switch ( s.substr( 0, 1 ) ) {
               case "[":
                  if ( s.substr( 1, 1 )  ===  "[" ) {
                     i = s.indexOf( "]]", 2 );
                     if ( i > 0 ) {
                        s = s.substr( 0, i );
                        i = s.indexOf( "|", 1 );
                        if ( i > 0 ) {
                           s = s.substr( 0, i );
                        }
                        if ( s.indexOf( ":", 2 )  >  0 ) {
                           mode = 2;
                        } else {
                           s = false;
                        }
                     } else {
                        s = false;
                     }
                  } else if ( s.indexOf( "//" )  >  0 ) {
                     mode = 1;
                  }
                  if ( mode ) {
                     s = s.substr( mode );
                  }
                  break;
               case "{":
                  if ( s.substr( 1, 1 )  ===  "{" ) {
                     s = DESC.fold( s );
                     if ( s ) {
                        mode = 3;
                     }
                  }
                  break;
               default:
                  mode = 0;
                  i = s.indexOf( "\n", 5 );
                  if ( i > 0 ) {
                     s = s.substr( 0, i );
                  }
                  i = s.indexOf( "]]", 2 );
                  if ( i > 0 ) {
                     s = s.substr( 0, i );
                  }
                  break;
            }   // switch s.substr( 0, 1 )
            if ( s ) {
               if ( mode ) {
                  s = s.replace( /^\s+/, "" )
                       .replace( /\s+$/, "" );
                  if ( mode === 1   &&   s.substr( 0, 2 )  ===  "//" ) {
                     s = window.location.protocol + s;
                  }
               }
               got = /^([a-zA-Z]+)\b/.exec( s );
               if ( got ) {
                  start = got[ 1 ].toLowerCase();
                  if ( typeof DESC[ start ]  ===  "function"
                       &&   start !== "fire" ) {
                     r = DESC[ start ]( s, mode );
                  }
               }
            }
         }
      }
      return r;
   };   // DESC.fire()



   DESC.fold = function ( attempt ) {
      // Analyse presumable template transclusion
      // Precondition:
      //    attempt   -- string with query, beginning with "{{"
      // Postcondition:
      //    Returns string with URL or keyword/data, or false
      // Uses:
      //    >  PROJ
      //    mw.config.get()
      // 2017-05-17 PerfektesChaos@de.wikipedia
      var r = false,
          env, got, i, ns, sign, space, suffix;
      got = /^({{\s*([^|}]+)\s*)[|}]/.exec( attempt );
      if ( got ) {
         suffix = attempt.substr( got[ 1 ].length )
                         .replace( /\n/g, " " );
         sign   = got[ 2 ].replace( /\n/g, " " );
         i      = sign.indexOf( ":" );
         if ( i >= 0 ) {
            env = mw.config.get( [ "wgNamespaceIds",
                                   "wgFormattedNamespaces" ] );
            if ( i ) {
               space = sign.substr( 0, i )
                           .toLowerCase()
                           .replace( /\s+/g, "_" )
                           .replace( /__+/g, "_" );
               ns    = env.wgNamespaceIds[ space ];
               if ( ! ns ) {
                  ns = -9;
               }
            } else {
               ns = 0;
            }
            if ( ns >= 0 ) {
               sign = sign.substr( i + 1 ).replace( /^\s+/, "" );
               sign = env.wgFormattedNamespaces[ "" + ns ]  +  ":"
                      + sign.substr( 0, 1 )
                      + sign.substr( 1 );
            }
         }
         sign = sign.replace( /\s+/g, "_" )
                    .replace( /__+/g, "_" )
                    .replace( /_$/, "" );
         switch ( sign.toUpperCase() ) {
            case "ARXIV":
            case "DNB":
            case "DOI":
            case "ISBN":
            case "LCCN":
            case "OCLC":
            case "PMC":
            case "PMID":
            case "RFC":
               got = /^\|\s*([^|}]+)\s*[|}]/.exec( suffix );
               if ( got ) {
                  r = sign.toUpperCase() + " " + got[ 1 ];
               }
               break;
            default:
               if ( typeof PROJ.templates  ===  "object" &&
                    typeof PROJ.templates[ sign ]  ===  "function" ) {
                  r = PROJ.templates[ sign ]( suffix );
               }
         }   // switch sign
      }
      return r;
   };   // DESC.fold()



   DESC.arxiv = function ( attempt ) {
      // Analyse request string, presumably arXiv
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // 2015-05-10 PerfektesChaos@de.wikipedia
      var got = /^arxiv\s*[:=|]?\s*(\S+)\s*/i.exec( attempt ),
          r, s;
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "arxiv",
               arxiv:  s,
               url:    "http://arxiv.org/abs/" + s,
               say:    "arXiv " + s };
      }
      return r;
   };   // DESC.arxiv()



   DESC.dnb = function ( attempt ) {
      // Analyse request string, presumably Deutsche Nationalbibliothek
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // 2015-05-10 PerfektesChaos@de.wikipedia
      var got = /^dnb\s*[:=|]?\s*([1-9][0-9]+X?)\b/i.exec( attempt ),
          r, s;
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "dnb",
               dnb:    s,
               url:    "http://d-nb.info/" + s,
               say:    "DNB " + s };
      }
      return r;
   };   // DESC.dnb()



   DESC.doi = function ( attempt, achieved ) {
      // Analyse request string, presumably DOI
      // Precondition:
      //    attempt   -- string with query
      //    achieved  -- number of mode
      // Postcondition:
      //    Returns object, or not
      // 2015-06-30 PerfektesChaos@de.wikipedia
      var got, i, r, s;
      switch ( achieved ) {
         case 0:
            got = /^doi\s*[:=]?\s*(\S+)(?:\s.+)?$/i.exec( attempt );
            break;
         case 2:
            got = /^doi:\s*(\S+)$/i.exec( attempt );
            break;
         case 3:
            got = /^doi\s*\|([^|]+)(?:\|.+)?$/i.exec( attempt );
            break;
      }   // switch achieved
      if ( got ) {
         s = got[ 1 ].replace( /^\s+/, "" )
                     .replace( /\s+$/, "" );
         i = s.indexOf( "}}" );
         if ( i  >  0   &&   s.indexOf( "{", i )  < 0 ) {
            s = s.substr( 0, i );
         }
         r = { scheme: "doi",
               doi:    s,
               say:    "doi:" + s };
         if ( /^10\.[1-9]\d\d\d+\/\S+$/.test( s ) ) {
            r.url = "http://dx.doi.org/" + s;
         }
      }
      return r;
   };   // DESC.doi()



   DESC.ftp = function ( attempt ) {
      // Analyse request string, presumably URL
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // Uses:
      //    this
      //    DESC.http()
      // 2015-05-01 PerfektesChaos@de.wikipedia
      return this.http( attempt );
   };   // DESC.ftp()



   DESC.http = function ( attempt ) {
      // Analyse request string, presumably URL
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // Uses:
      //    >  GUIP.caret.starter
      //    >< DESC.re.url
      // 2015-09-07 PerfektesChaos@de.wikipedia
      var got = /(\S+)\s?/.exec( attempt ),
          i, r, re, s, site, suffix;
      if ( got ) {
         if ( typeof DESC.re.url  !==  "object" ) {
            s = "^((?:f|ht)tps?://"
                + "(?:[^./]+\\.)+\\w+"
                + "(?::[0-9]+)?)"
                + "(/.*)$";
            DESC.re.url = new RegExp( s, "i" );
         }
         s   = got[ 1 ];
         r   = { scheme: "url" };
         got = DESC.re.url.exec( s );
         if ( got ) {
            site   = got[ 1 ].toLowerCase();
            suffix = got[ 2 ];
            i      = suffix.indexOf( "]" );
            if ( i > 0 ) {
               suffix = suffix.substr( 0, i );
            }
            i = suffix.indexOf( "|" );
            if ( i > 0   &&
                 typeof GUIP.caret.starter  ===  "string" ) {
               if ( /[|=]\s*$/.test( GUIP.caret.starter ) ) {
                  // template parameter
                  suffix = suffix.substr( 0, i );
               }
            }
            re  = /^([^#<]+)([#<])(.*)$/;
            got = re.exec( suffix );
            if ( got ) {
               suffix = got[ 1 ];
               if ( got[ 2 ] === "#" ) {
                  r.subtle = got[ 3 ];
               }
            }
            r.url    = site + suffix;
            // TODO ///////// redirect URL
            r.source = r.url;
            got      = /[.\/]?([^.\/]+\.\w+)(:[0-9]+)?$/.exec( site );
            if ( got ) {
               r.say = got[ 1 ];
            }
         }
      }
      return r;
   };   // DESC.http()



   DESC.https = function ( attempt ) {
      // Analyse request string, presumably URL
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // Uses:
      //    this
      //    DESC.http()
      // 2015-05-01 PerfektesChaos@de.wikipedia
      return this.http( attempt );
   };   // DESC.https()



   DESC.isbn = function ( attempt, achieved ) {
      // Analyse request string, presumably ISBN
      // Precondition:
      //    attempt   -- string with query
      //    achieved  -- number of mode
      // Postcondition:
      //    Returns object, or not
      // Uses:
      //    .isbnLib.focus()
      //    .isbnLib.finder()
      // 2016-03-29 PerfektesChaos@de.wikipedia
      var got, i, isbnLib, r;
      switch ( achieved ) {
         case 0:
         case 2:
            got = /^isbn\s*[:=]?\s*([-0-9]+x?.*)$/i.exec( attempt );
            break;
      }   // switch achieved
      if ( got ) {
         r   = { scheme: "isbn",
                 isbn:   got[ 1 ],
                 lang:   false };
         got = /^([-0-9]+x?)(?:[^-0-9x].*)?$/i.exec( r.isbn );
         if ( got ) {
            isbnLib = CITWT.isbnLib;
            r.isbn = got[ 1 ].replace( /-/g, "" );
            switch ( r.isbn.length ) {
               case 10:
                  r.isbn = r.isbn.toUpperCase();
                  if ( /^[0-9]+X?$/.test( r.isbn ) ) {
                     r.lang = isbnLib.focus( r.isbn );
                     r.url  = true;
                  }
                  break;
               case 13:
                  got = /^97([89])[0-9]+$/.exec( r.isbn );
                  if ( got ) {
                     if ( got[ 1 ] === "8" ) {
                        r.lang = isbnLib.focus( r.isbn.substr( 3 ) );
                     } else {
                        r.lang = isbnLib.focus( r.isbn );
                     }
                     r.url = true;
                  }
                  break;
            }   // switch n
            if ( r.url ) {
               r.url = isbnLib.finder( r.lang );
               r.say = r.isbn;
               r.url.push( "/wiki/Special:Booksources/#" );
               for ( i = 0;  i < r.url.length;  i++ ) {
                  r.url[ i ] = r.url[ i ].replace( /#/, r.isbn );
               }   // for i
               i = r.url.length - 1;
               r.url[ i ] = [ r.url[ i ], "ISBN" ];
            }
         } else {
            r.isbn = r.isbn.substr( 0, 30 );
         }
      }
      return r;
   };   // DESC.isbn()



   DESC.lccn = function ( attempt ) {
      // Analyse request string, like Library of Congress Control Number
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // 2015-05-10 PerfektesChaos@de.wikipedia
      var got = /^lccn\s*[:=|]?\s*([-a-z/0-9]+)\b/i.exec( attempt ),
          r, s;
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "lccn",
               lccn:   s,
               url:    "http://lccn.loc.gov/" + s,
               say:    "LCCN " + s };
      }
      return r;
   };   // DESC.lccn()



   DESC.oclc = function ( attempt ) {
      // Analyse request string, presumably OCLC
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // 2015-05-10 PerfektesChaos@de.wikipedia
      var got = /^oclc\s*[:=|]?\s*([1-9][0-9]+)\b/i.exec( attempt ),
          r, s;
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "oclc",
               oclc:   s,
               url:    "http://worldcat.org/oclc/" + s,
               say:    "OCLC " + s };
      }
      return r;
   };   // DESC.oclc()



   DESC.pmc = function ( attempt ) {
      // Analyse request string, presumably PMC
      // Precondition:
      //    attempt  -- string with query
      // Postcondition:
      //    Returns object, or not
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var got = /^pmc\s*[:=|]?\s*([1-9][0-9]+)\b/i.exec( attempt ),
          r, s;
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "pmc",
               pmc:    s,
               url:    "http://www.ncbi.nlm.nih.gov/pmc/articles/PMC"
                       + s,
               say:    "PMC " + s };
      }
      return r;
   };   // DESC.pmc()



   DESC.pmid = function ( attempt, achieved ) {
      // Analyse request string, presumably PMID
      // Precondition:
      //    attempt   -- string with query
      //    achieved  -- number of mode
      // Postcondition:
      //    Returns object, or not
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var got, r, s;
      switch ( achieved ) {
         case 0:
         case 2:
            got = /^pmid\s*[:=]?\s*([1-9][0-9]+)\b/i.exec( attempt );
            break;
      }   // switch achieved
      if ( got ) {
         s = got[ 1 ];
         r = { scheme: "pmid",
               pmid:   s,
               url:    "https://www.ncbi.nlm.nih.gov/pubmed/" + s,
               say:    "PMID " + s };
      }
      return r;
   };   // DESC.pmid()



//-----------------------------------------------------------------------



   GUIP.gallery = {
     buttonTool: "00/Button_easy_cite_%281%29.png",
     throbber:   "de/Ajax-loader.gif",
     wikiEditor: "67/WikiEditor_Cite.svg",
     MISSING:    "45/%D0%95%D0%B3%D0%B3%D0%BE%D0%B3.svg"
   };   // 2015-05-01 PerfektesChaos@de.wikipedia
   GUIP.$panel = false;



   GUIP.factory = function () {
      // Equip page GUI with panel
      // Uses:
      //    >  .type
      //    >  GUIP.btn.show
      //    >  .vsn
      //    >  GUIP.$textarea
      //     < GUIP.live
      //     < GUIP.oneof
      //     < GUIP.$panel
      //     < GUIP.$button
      //     < GUIP.input.$text
      //     < GUIP.push.$bar
      //    GUIP.gadget.factory{)
      //    GUIP.fiat()
      //    (TASK.fiat)
      // 2015-06-16 PerfektesChaos@de.wikipedia
      var collection = [ "empty",
                         "invalid",
                         "throbber",
                         "translated",
                         "unanswered",
                         "yippie" ],
          i, $editarea;
      GUIP.live   = false;
      GUIP.oneof  = { };
      GUIP.$panel = $( "<div>" );
      GUIP.$panel.attr( { "id":  CITWT.type } );
      GUIP.$panel.css( { "border":        "#808080 2px solid",
                         "margin-top":    "0.5em",
                         "margin-bottom": "0.5em",
                         "overflow":      "auto",
                         "padding":       "5px" } );
      GUIP.$panel.hide();
      GUIP.$button = $( "<button>" );
      GUIP.$button.attr( { "id":    CITWT.type + "-button",
                           "title": CITWT.vsn } );
      GUIP.$button.click( TASK.fiat );
      GUIP.$button.css( { "float":        "left",
                          "margin-right": "2em" } );
      GUIP.$button.text( GUIP.btn.show );
      GUIP.$panel.append( GUIP.$button );
      GUIP.gadget.factory();
      for ( i = 0;  i < collection.length;  i++ ) {
         GUIP.fiat( collection[ i ] );
      }   // for i
      GUIP.input.$text = false;
      GUIP.push.$bar   = false;
      $editarea        = $( "#edithelper-panel");   // user:Schnark
      if ( ! $editarea.length ) {
         if ( $( "#syntaxhighlight").length ) {
            // hack Schnark/js/syntaxhighlight.js
            $editarea = GUIP.$textarea.parent();
         }
         if ( ! $editarea.length ) {
            $editarea = GUIP.$textarea;
         }
      }
      $editarea.before( GUIP.$panel );
   };   // GUIP.factory()



   GUIP.fiat = function ( achieve ) {
      // Create element
      // Precondition:
      //    achieve  -- keyword
      //                * empty
      //                * invalid
      //                * throbber
      //                * translated
      //                * unanswered
      //                * yippie
      // Uses:
      //    >  .type
      //    >  GUIP.oneof
      //    >  GUIP.$panel
      //    GUIP.file()
      //    I18N.fair()
      // 2015-05-25 PerfektesChaos@de.wikipedia
      var $elem   = $( "<span>" ),
          colours = { empty:      "808080",
                      invalid:    "FF0000",
                      translated: "208020",
                      unanswered: "FF0000",
                      yippie:     "40C040" },
          $img, $span;
      $elem.attr( { id:  CITWT.type + "_" + achieve } );
      $elem.css( { "vertical-align": "top" } );
      if ( achieve === "throbber" ) {
         $img  = $( "<img />" );
         $img.attr( { src:    GUIP.file( "throbber" ),
                      id:     CITWT.type + "_throbber_ajax",
                      height: "32",
                      alt:    "Ajax" } );
         $elem.append( $img );
      } else {
         $span = $( "<span>" );
         $span.css( { "color":         "#" + colours[ achieve ],
                      "font-size":     "200%",
                      "font-weight":   "bold",
                      "margin-right":  "1em" } );
         $span.text( I18N.fair( "msg"
                                + achieve.substr( 0, 1 ).toUpperCase()
                                + achieve.substr( 1 ),
                                true ) );
         $elem.append( $span );
      }
      if ( "|invalid|throbber|translated|".indexOf( achieve )  >  0 ) {
         $span = $( "<span>" );
         $span.attr( { id:  CITWT.type + "_" + achieve + "_text" } );
         $span.css( { "margin-left": "1em" } );
         if ( achieve === "invalid" ) {
            $span.css( { "color":       "#FF0000",
                         "font-weight": "bold" } );
         }
         $elem.append( $span );
      } else if ( achieve === "unanswered" ) {
         $elem.css( { "letter-spacing": "0.13em" } );
      }
      $elem.hide();
      GUIP.oneof[ "$" + achieve ] = $elem;
      GUIP.$panel.append( $elem );
   };   // GUIP.fiat()



   GUIP.file = function ( access ) {
      // Retrieve image URL
      // Precondition:
      //    access  -- keyword
      // Postcondition:
      //    Returns any image access string
      // Uses:
      //    >  GUIP.gallery
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var r = GUIP.gallery[ access ];
      if ( ! r ) {
         r = GUIP.gallery.MISSING;
      }
      return "//upload.wikimedia.org/wikipedia/commons/"
             +  r.substr(0, 1)  +  "/"  +  r;
   };   // GUIP.file()



   GUIP.fire = function () {
      // Start page GUI equipping
      // Uses:
      //    $()
      //    (GUIP.first)
      // Remark: Used as event handler -- 'this' is not GUIP
      // 2015-05-12 PerfektesChaos@de.wikipedia
      $( GUIP.first );
   };   // GUIP.fire()



   GUIP.first = function () {
      // Equip page GUI with DOM elements
      // Precondition:
      //    DOM ready
      //    mediawiki modules and user options/config have been loaded
      // Uses:
      //    >  GUIP.$panel
      //    >  .bibRecord
      //    >  .leading   (user defined)
      //     < GUIP.$textarea
      //     < GUIP.submit
      //     < GUIP.btn.show
      //    I18N.fair()
      //    GUIP.factory()
      //    GUIP.btn.fire()
      //    mw.loader.using()
      //    GUIP.flat()
      //    mw.loader.load()
      //    (.fire);
      // Remark: Used as event handler -- 'this' is not GUIP
      // 2017-02-20 PerfektesChaos@de.wikipedia
      var require;
      GUIP.$textarea = $( "#wpTextbox1" );
      if ( GUIP.$textarea.length  &&  ! GUIP.$panel ) {
         GUIP.submit   = false;
         GUIP.btn.show = I18N.fair( "Citoid", true );
         GUIP.factory();
         GUIP.btn.fire();
         require = [ "jquery.textSelection",
                     "mediawiki.api" ];
         if ( CITWT.bibRecord && CITWT.bibRecord.launch ) {
            mw.loader.using( require, CITWT.fire );
         } else {
            if ( typeof CITWT.leading  ===  "boolean"
                 &&     CITWT.leading ) {
               GUIP.flat( false );
            }
            mw.loader.load( require );
         }
      }
   };   // GUIP.first()



   GUIP.flat = function ( access, $add ) {
      // Disable main button, rise cleared panel; show message
      // Precondition:
      //    access  -- keyword, if any
      //               -- false: do not discard text field
      //    $add    -- additional jQuery object (second call only)
      // Uses:
      //    >  GUIP.$button
      //    >  GUIP.oneof
      //    >  .vsn
      //    >  GUIP.$panel
      //     < GUIP.live
      //    GUIP.input.free()
      //    GUIP.input.fire()
      // 2015-06-16 PerfektesChaos@de.wikipedia
      var leave, s, $child, $elem;
      if ( access ) {
         if ( access !== true ) {
            $elem  = GUIP.oneof[ "$" + access ];
            $child = $elem.children().eq( 1 );
            if ( $child.length ) {
               $child.empty();
            }
            s = "|empty|invalid|unanswered|";
            if ( s.indexOf( access )  >  0 ) {
               GUIP.input.free();
            }
         }
      } else if ( access !== false ) {
         GUIP.input.fire( true );
      }
      if ( $add ) {
         if ( $child ) {
            $child.append( $add );
         }
      } else {
         leave = ( access === "throbber" );
         GUIP.$button.attr( { "disabled": leave,
                              "title":    CITWT.vsn } );
         for ( s in GUIP.oneof ) {
            GUIP.oneof[ s ].hide();
         }   // for $elem in .oneof
         GUIP.push.flip( 0 );
         GUIP.$panel.show();
         GUIP.live = true;
         if ( $elem ) {
            $elem.show();
         }
      }
   };   // GUIP.flat()



   GUIP.$flip = function ( $assembly, add, assign ) {
      // Create sequence of external links
      // Precondition:
      //    $assembly  -- <span> or nothing
      //    add        -- add Array[2]
      //                      -- [0]  URL string
      //                      -- [1]  title string
      //    assign     -- serial number
      // Postcondition:
      //    Returns extended $assembly
      // Uses:
      //     < GUIP.$info
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var $a = $( "<a>" ),
          $r;
      if ( $assembly ) {
         $r = $assembly;
      } else {
         $r         = $( "<span>" );
         GUIP.$info = false;
      }
      $a.attr( { href:    add[ 0 ],
                 target:  "CITWT" + assign } );
      $a.css( { "margin-right": "0.75em" } );
      $a.text( add[ 1 ] );
      $r.append( $a );
      return $r;
   };   // GUIP.$flip()



   GUIP.$flipper = function ( $assembly, add ) {
      // Add identifier element to query info
      // Precondition:
      //    $assembly  -- <span>
      //    add        -- displayed string
      // Uses:
      //     < GUIP.$info
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.$info = $( "<span>" );
      GUIP.$info.css( { "margin-right": "0.75em" } );
      GUIP.$info.text( add );
      $assembly.append( GUIP.$info );
   };   // GUIP.$flipper()



   GUIP.flop = function ( about ) {
      // Show error message on invalid data format
      // Precondition:
      //    about  -- object with details
      //              -- about.scheme          data type
      //              -- about[about.scheme]   data content
      // Uses:
      //    I18N.fair()
      //    GUIP.flat()
      // 2015-05-01 PerfektesChaos@de.wikipedia
       var story = I18N.fair( "invalid" ),
           $msg  = $( "<span>" ),
           $span = $( "<span>" ),
           $code;
      GUIP.flat( "invalid" );
      if ( typeof about.scheme  ===  "string" ) {
         story = about.scheme.toUpperCase() + " &#8211; " + story;
         if ( typeof about[ about.scheme ]  ===  "string" ) {
            $code = $( "<code>" );
            $code.css( { "margin-left": "1.5em" } );
            $code.text( about[ about.scheme ] );
         }
      }
      $span.html( story );
      $msg.append( $span );
      if ( $code ) {
         $msg.append( $code );
      }
      GUIP.flat( "invalid", $msg );
   };   // GUIP.flop()



   GUIP.btn.fire = function () {
      // Equip page GUI with general button
      // Uses:
      //    this
      //    >  GUIP.opts
      //    >< GUIP.submit
      //    mw.loader.using()
      //    GUIP.btn.wikEd()
      //    GUIP.btn.toolbar()
      //    GUIP.btn.portlet()
      //    (GUIP.btn.wikiEditor)
      // 2018-08-24 PerfektesChaos@de.wikipedia
      if ( ! this.wikEd() ) {
         if ( GUIP.opts.usebetatoolbar ) {
            GUIP.submit = "wikiEditor ... (loading)";
            mw.loader.using( [ "ext.wikiEditor" ],
                             this.wikiEditor );
         } else if ( GUIP.opts.showtoolbar ) {
            this.toolbar();
         }
      }
      if ( ! GUIP.submit ) {
         this.portlet();
      }
   };   // GUIP.btn.fire()



   GUIP.btn.portlet = function () {
      // Equip wikiEditor toolbar with button
      // Uses:
      //    >  .type
      //    >  GUIP.btn.show
      //     < GUIP.submit
      //    mw.util.addPortletLink()
      //    (.fire)
      // 2015-05-12 PerfektesChaos@de.wikipedia
      var portlet  =  mw.util.addPortletLink( "p-tb",
                                              "#",
                                              GUIP.btn.show,
                                              "t-" + CITWT.type );
      $( portlet ).click( CITWT.fire );
      GUIP.submit = "portlet-tb";
   };   // GUIP.btn.portlet()



   GUIP.btn.toolbar = function () {
      // Equip classic toolbar with button
      // Uses:
      //    >  .type
      //    >  GUIP.btn.show
      //     < GUIP.submit
      //    GUIP.file()
      //    (.fire)
      // 2015-05-12 PerfektesChaos@de.wikipedia
      var $toolbar = $( "#toolbar" ),
          $img;
      if ( $toolbar.length ) {
         $img = $( "<img />" );
         $img.attr( { "src":    GUIP.file( "buttonTool" ),
                      "id":     CITWT.type + "-toolbar",
                      "height": "22",
                      "width":  "23",
                      "alt":    GUIP.btn.show,
                      "title":  GUIP.btn.show } );
         $img.click( CITWT.fire );
         $toolbar.prepend( $img );
         GUIP.submit = "toolbar";
      }
   };   // GUIP.btn.toolbar()



   GUIP.btn.wikEd = function () {
      // Equip wikEd GUI with button
      // Postcondition:
      //    Returns something, if equipped for wikEd
      // Uses:
      //    >  wikEd
      //    >  .type
      //    >  GUIP.btn.show
      //     < GUIP.submit
      //     < EDIT.wikEd
      //    mw.user.options.get()
      //    GUIP.file()
      // 2018-07-21 PerfektesChaos@de.wikipedia
      var id = 746,
          cnf;
      if ( mw.user.options.get( "gadget-wikEd" )
           ||  ( typeof window.wikEd  ===  "object"
                 &&     window.wikEd ) ) {
         if ( typeof window.wikEd  !==  "object" ) {
            window.wikEd = {  config: { }  };
         }
         EDIT.wikEd = window.wikEd;
         if ( typeof EDIT.wikEd.config  !==  "object" ) {
            EDIT.wikEd.config = { button:    { },
                                    buttonBar: { } };
         }
         cnf = EDIT.wikEd.config;
         if ( typeof cnf.button  !==  "object" ) {
            cnf.button = { };
         }
         cnf.button[ id ] = [ "wikEdCitoid",
                              "wikEdButton",
                              GUIP.btn.show,
                              GUIP.file( "buttonTool" ),
                              "16", "16",
                              "DIV",
                              "window.mediaWiki.libs."
                              + CITWT.type
                              + ".fire();" ];
         if ( typeof cnf.buttonBar  !==  "object" ) {
            cnf.buttonBar = { };
         }
         if ( typeof cnf.buttonBar.custom1  !==  "object" ) {
            cnf.buttonBar.custom1 = [ "wikEdButtonBarCustom1",
                                      "wikEdButtonBarCustom1",
                                      "wikEdButtonsCustom1",
                                      "wikEdButtonsCustom1",
                                      44,
                                      GUIP.btn.show,
                                      [ ] ];
         }
         cnf.buttonBar.custom1[ 6 ].push( id );
         if ( typeof EDIT.wikEd.disabled         ===  "boolean"    &&
              typeof EDIT.wikEd.highlightSyntax  ===  "boolean"    &&
              typeof EDIT.wikEd.turnedOn         ===  "boolean"    &&
              typeof EDIT.wikEd.useWikEd         ===  "boolean"
              &&     EDIT.wikEd.useWikEd ) {
            GUIP.submit = "wikEd";
         }
      }
      return GUIP.submit;
   };   // GUIP.btn.wikEd()



   GUIP.btn.wikiEditor = function () {
      // Equip wikiEditor toolbar with button
      // Precondition:
      //    ext.wikiEditor.toolbar has been loaded
      // Uses:
      //    >  GUIP.$textarea
      //    >  .type
      //    >  GUIP.btn.show
      //     < GUIP.submit
      //    GUIP.file()
      //    (.fire)
      // 2015-05-12 PerfektesChaos@de.wikipedia
      GUIP.$textarea.wikiEditor( "addToToolbar",
                    { "section": "main",
                      "group":   "format",
                      "tools":   { citoid:
                                     { type:   "button",
                                       label:  GUIP.btn.show,
                                       icon:   GUIP.file( "wikiEditor" ),
                                       action: { type:    "callback",
                                                 execute: CITWT.fire
                                               }
                                     }
                                 }
                    } );
      GUIP.submit = "wikiEditor";
   };   // GUIP.btn.wikiEditor()



   GUIP.caret.fetch = function ( after ) {
      // Get selection
      // Precondition:
      //    after  -- callback function
      // Postcondition:
      //    selection assigned
      // Uses:
      //    this
      //     < GUIP.caret.min
      //     < GUIP.caret.max
      //     < GUIP.caret.selection
      //     < GUIP.caret.starter
      //     < GUIP.caret.further
      //    GUIP.caret.wikEd()
      //    mw.loader.using()
      //    (GUIP.caret.find)
      // 2015-05-19 PerfektesChaos@de.wikipedia
      this.min       = -2;
      this.max       = -3;
      this.selection = false;
      this.starter   = false;
      if ( this.wikEd() ) {
         after( "wikEd" );
      } else {
         this.further = after;
         mw.loader.using( [ "jquery.textSelection" ],
                          this.find );
      }
   };   // GUIP.caret.fetch()



   GUIP.caret.fill = function ( apply ) {
      // Insert/replace string at selection
      // Precondition:
      //    apply  -- string to be added
      // Postcondition:
      //    edited text modified
      // Uses:
      //    this
      //     < GUIP.caret.shift
      //    GUIP.caret.fetch()
      //    (GUIP.caret.filler)
      // 2015-05-01 PerfektesChaos@de.wikipedia
      this.shift = apply;
      this.fetch( this.filler );
   };   // GUIP.caret.fill()



   GUIP.caret.filler = function ( alien ) {
      // Insert/replace string at selection
      // Precondition:
      //    alien  -- string with name of editor, or false
      // Postcondition:
      //    edited text modified
      // Uses:
      //    >  GUIP.caret.shift
      //    >  GUIP.caret.story
      //    >  GUIP.caret.min
      //    >  GUIP.caret.max
      //    >  GUIP.$textarea
      //    >  EDIT.codeMirror
      //    >  EDIT.wikEd
      // Remark: Used as event handler -- 'this' is not GUIP.caret
      // 2018-07-21 PerfektesChaos@de.wikipedia
      var shift, start, story;
      if ( GUIP.caret.max >= 0 ) {
         shift = GUIP.caret.shift;
         story = GUIP.caret.story;
         start = story.substr( 0, GUIP.caret.min );
         story = story.substr( GUIP.caret.max );
         if ( shift.substr( 0, 4 ) === "<ref" ) {
            start = start.replace( /\s+$/, "" );
         } else if ( shift.substr( 0, 1 ) === "\n"   &&
                     /\n$/.test( start ) ) {
            shift = shift.substr( 1 );
         }
         if ( story.substr( 0, 1 ) === "\n"   &&
              /\n$/.test( shift ) ) {
            story = story.substr( 1 );
         }
         story = start + shift + story;
         GUIP.$textarea.val( story );
         if ( EDIT.codeMirror && EDIT.codeMirror.liveCITWT ) {
            EDIT.codeMirror.doc.setValue( story );
         } else if ( alien === "wikEd" ) {
            EDIT.wikEd.UpdateFrame();
         }
      }
   };   // GUIP.caret.filler()



   GUIP.caret.find = function () {
      // Get selection in regular textarea, but perhaps CodeMirror
      // Precondition:
      //    jquery.textSelection has been loaded
      // Postcondition:
      //    selection assigned
      // Uses:
      //    >  CodeMirror
      //    >  GUIP.$textarea
      //    >  GUIP.caret.further
      //    >< EDIT.codeMirror
      //     < GUIP.caret.story
      //     < GUIP.caret.min
      //     < GUIP.caret.max
      //     < GUIP.caret.selection
      //     < GUIP.caret.starter
      // Remark: Used as event handler -- 'this' is not GUIP.caret
      // 2018-07-21 PerfektesChaos@de.wikipedia
      var rg, uo;
      if ( ! EDIT.codeMirror   &&
           typeof window.CodeMirror  ===  "function"   &&
           typeof window.CodeMirror.doc  ===  "object"
           &&     window.CodeMirror.doc ) {
         EDIT.codeMirror = window.CodeMirror;
      }
      if ( EDIT.codeMirror ) {
         uo = mw.user.options.get( "usecodemirror" );
         EDIT.codeMirror.liveCITWT = ( typeof uo  ===  "number"
                                       &&     uo > 0 );
         if ( EDIT.codeMirror.liveCITWT ) {
            GUIP.$textarea.val( EDIT.codeMirror.doc.getValue() );
         }
      }
      rg = GUIP.$textarea.textSelection( "getCaretPosition",
                                         { "startAndEnd": true } );
      GUIP.caret.story = GUIP.$textarea.val();
      if ( rg[0] <= rg[1] ) {
         GUIP.caret.min       = rg[0];
         GUIP.caret.max       = rg[1];
         GUIP.caret.selection = GUIP.caret.story.substr( rg[0] );
         GUIP.caret.starter   = GUIP.caret.story.substr( 0, rg[0] );
      }
      GUIP.caret.further();
   };   // GUIP.caret.find()



   GUIP.caret.wikEd = function () {
      // Get wikEd selection
      // Precondition:
      //    assign  -- object
      // Postcondition:
      //    Returns true, if selection assigned
      // Uses:
      //    this
      //    >  EDIT.wikEd
      //    >  GUIP.$textarea
      //     < GUIP.caret.start
      //     < GUIP.caret.stop
      //     < GUIP.caret.min
      //     < GUIP.caret.max
      //     < GUIP.caret.story
      //     < GUIP.caret.selection
      //     < GUIP.caret.starter
      //    (GUIP.caret.wikEdEnclose)
      // 2018-01-21 PerfektesChaos@de.wikipedia
      var r;
      if ( EDIT.wikEd &&
           ! EDIT.wikEd.disabled  &&
           EDIT.wikEd.useWikEd    &&
           EDIT.wikEd.turnedOn    &&
           EDIT.wikEd.highlightSyntax ) {
         this.start = "##\f#citoid_marker_begin##\f#";
         this.stop  = "##\f#citoid_marker_end##\f#";
         EDIT.wikEd.EditButton( null,
                                null,
                                null,
                                this.wikEdEnclose );
         EDIT.wikEd.UpdateTextarea();
         r         = GUIP.$textarea.val();
         this.min  = r.search( this.start );
         r         = r.replace( this.start, "" );
         this.max  = r.search( this.stop ) - 1;
         r         = r.replace( this.stop, "" );
         GUIP.$textarea.val( r );
         this.story     = r;
         this.selection = this.story.substr( this.min );
         this.starter   = this.story.substr( 0, this.min );
         EDIT.wikEd.UpdateFrame();
         r = true;
      }
      return r;
   };   // GUIP.caret.wikEd()



   GUIP.caret.wikEdEnclose = function ( assign ) {
      // Mark wikEd selection region
      // Precondition:
      //    assign  -- object
      // Postcondition:
      //    assign got further properties
      // Uses:
      //    >  EDIT.wikEd
      //    >  GUIP.caret.start
      //    >  GUIP.caret.stop
      // Remark: Used as event handler -- 'this' is not GUIP.caret
      // 2018-07-21 PerfektesChaos@de.wikipedia
      EDIT.wikEd.GetText( assign, "selection, cursor" );
      if ( assign.selection.plain === "" ) {
         assign.sel = EDIT.wikEd.GetSelection();
         if ( assign.sel.anchorNode.previousSibling ) {
            assign.changed = assign.cursor;
         } else {
            EDIT.wikEd.GetText( assign, "whole" );
            assign.changed = assign.whole;
         }
      } else {
         assign.changed = assign.selection;
      }
      assign.changed.plain = GUIP.caret.start
                             + assign.changed.plain
                             + GUIP.caret.stop;
   };   // GUIP.caret.wikEdEnclose()



   GUIP.gadget.factory = function () {
      // Create gadget bar
      // Uses:
      //    this
      //    >  GUIP.$panel
      //     < GUIP.gadget.$bar
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var buttons = [ "hide", "help", "hear", "home" ],
          i;
      this.$bar = $( "<div>" );
      this.$bar.attr( { "id":  CITWT.type + "-bar" } );
      this.$bar.css( { "float": "right" } );
      for ( i = 0;  i < 4;  i++ ) {
         this.$fiat( buttons[ i ] );
      }   // for i
      GUIP.$panel.append( this.$bar );
   };   // GUIP.gadget.factory()



   GUIP.gadget.$fiat = function ( assign ) {
      // Create a single gadget button and add to bar
      // Precondition:
      //    assign  -- string as identifier
      // Uses:
      //    this
      //    >  GUIP.gadget.$bar
      // 2015-05-12 PerfektesChaos@de.wikipedia
      var colours = { hear: "000000",
                      help: "208020",
                      hide: "FF0000",
                      home: "0000FF" },
          suffix  = assign.substr( 0, 1 ).toUpperCase()
                    + assign.substr( 1 ),
          $btn    = $( "<button>" ),
          $span   = $( "<span>" );
      $btn.attr( { "id":    CITWT.type + "-button-" + assign,
                   "title": I18N.fair( "tip" + suffix,  true )  } );
      $btn.click( this[ assign ] );
      $btn.css( { "float":       "right",
                  "margin-left": "2px" } );
      $span.css( { "color":       "#" + colours[ assign ],
                   "font-weight": "bold" } );
      $span.html( I18N.fair( "gadget" + suffix,  true ) );
      $btn.append( $span );
      this.$bar.append( $btn );
   };   // GUIP.gadget.$fiat()



   GUIP.gadget.hear = function ( event ) {
      // Gadget button for input field has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    UTIL.fullstop()
      //    GUIP.input.fire()
      // Remark: Used as event handler -- 'this' is not GUIP.gadget
      // 2015-05-12 PerfektesChaos@de.wikipedia
      UTIL.fullstop( event );
      GUIP.input.fire( false );
      GUIP.input.$text.show();
   };   // GUIP.gadget.hear()



   GUIP.gadget.help = function ( event ) {
      // Gadget button for help page has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    >  HELP.support
      //    UTIL.fullstop()
      //    I18N.fair()
      //    mw.util.getUrl()
      // Remark: Used as event handler -- 'this' is not GUIP.gadget
      // 2018-07-20 PerfektesChaos@de.wikipedia
      var support = "https://"  +  I18N.fair( "helpSite", true )
                    + ".wikipedia.org"
                    + mw.util.getUrl( HELP.support );
      UTIL.fullstop( event );
      window.open( support, "citWThelp" );
   };   // GUIP.gadget.help()



   GUIP.gadget.hide = function ( event ) {
      // Gadget button has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    >  GUIP.$panel
      //     < GUIP.live
      //    GUIP.gadget.home()
      // Remark: Used as event handler -- 'this' is not GUIP.gadget
      // 2015-06-16 PerfektesChaos@de.wikipedia
      GUIP.gadget.home( event );
      GUIP.$panel.hide();
      GUIP.live = false;
   };   // GUIP.gadget.hide()



   GUIP.gadget.home = function ( event ) {
      // Gadget button has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    UTIL.fullstop()
      //    GUIP.flat()
      // Remark: Used as event handler -- 'this' is not GUIP.gadget
      // 2015-05-12 PerfektesChaos@de.wikipedia
      UTIL.fullstop( event );
      GUIP.flat();
   };   // GUIP.gadget.home()



   GUIP.input.filler = function ( assign ) {
      // Retrieve text input content, disable field with content, or hide
      // Precondition:
      //    assign  -- string to be assigned to the invisible field
      // Postcondition:
      //    Returns non-empty input text, or false
      //    Hide empty field, disable field with content
      // Uses:
      //    this
      //    >  GUIP.input.$text
      //    >  GUIP.input.live
      //    GUIP.input.fire()
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var r;
      if ( GUIP.input.$text ) {
         r = this.$text.val()
                       .replace( /^\s+/, "" )
                       .replace( /\s+$/, "" )
                       .replace( /\s+ /, " " );
         this.$text.val( r );
         if ( ! this.live ) {
            if ( assign ) {
               this.$text.val( assign );
            }
            r = false;
         } else if ( r ) {
            this.$text.attr( { "disabled": true } );
         } else {
            this.fire( true );
            r = false;
         }
      }
      return r;
   };   // GUIP.input.filler()



   GUIP.input.fed = function ( event ) {
      // Keypress event on input field
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    >  GUIP.input.live
      //    >  GUIP.input.$text
      //    CITWT.fire()
      // Remark: Used as event handler -- 'this' is not GUIP.input
      // 2015-06-16 PerfektesChaos@de.wikipedia
      if ( ( event.which === 10  ||  event.which === 13 )
           &&   GUIP.input.live
           &&   GUIP.input.$text.is( ":focus" ) ) {
         CITWT.fire( event );
      }
   };   // GUIP.input.fed()



   GUIP.input.fire = function ( abort ) {
      // Set or toggle input field visibility
      // Precondition:
      //    abort  -- true: unconditionally vanishing
      // Uses:
      //    >  .type
      //    >  GUIP.$panel
      //    >< GUIP.input.$text
      //    >< GUIP.input.live
      //    GUIP.flat()
      //    GUIP.input.free()
      //    GUIP.input.filler()
      //    (GUIP.input.fed)
      // Remark: Used as event handler -- 'this' is not GUIP.input
      // 2015-05-25 PerfektesChaos@de.wikipedia
      if ( abort ) {
         GUIP.input.live = false;
      } else {
         GUIP.input.live = ( ! GUIP.input.live );
      }
      if ( GUIP.input.live ) {
         if ( ! GUIP.input.$text ) {
            GUIP.input.$text = $( "<input />" );
            GUIP.input.$text.addClass( "mw-ui-input NOIME" );
            GUIP.input.$text.attr( { id:   CITWT.type + "_text_input",
                                     size: 40,
                                     type: "text" } );
            GUIP.$panel.append( GUIP.input.$text );
         }
         GUIP.flat( false );
         GUIP.input.free();
         GUIP.input.$text.show();
         GUIP.input.$text.focus();
         GUIP.input.$text.keypress( GUIP.input.fed );
      } else if ( GUIP.input.$text ) {
         if ( abort === true ) {
            GUIP.input.$text.hide();
         }
         if ( abort !== true ) {
            GUIP.input.filler();
         }
      }
   };   // GUIP.input.fire()



   GUIP.input.free = function () {
      // Enable text input
      // Uses:
      //    this
      //    >  GUIP.input.live
      //    >  GUIP.input.$text
      // 2015-05-25 PerfektesChaos@de.wikipedia
      if ( this.live && this.$text ) {
         this.$text.attr( { "disabled": false } );
      }
   };   // GUIP.input.free()



   GUIP.push.$fiat = function ( assign ) {
      // Create a single result push button and add to bar
      // Precondition:
      //    assign  -- string as identifier
      // Uses:
      //    this
      //    >  GUIP.push.$bar
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var s     = "$" + assign,
          $span = $( "<span>" ),
          $btn;
      this[ s ] = $( "<button />" );
      $btn      = this[ s ];
      $btn.attr( { "id":  CITWT.type + "-push-" + assign } );
      $btn.click( this[ assign ] );
      $btn.css( { "float":       "right",
                  "margin-left": "1em" } );
      $span.css( { "white-space": "nowrap" } );
      $span.html( I18N.fair( assign, true ) );
      $btn.append( $span );
      if ( assign === "bibSection" ) {
         $btn.hide();
      }
      this.$bar.append( $btn );
   };   // GUIP.push.$fiat()



   GUIP.push.fired = function ( event, action ) {
      // Insertion button has been clicked
      // Precondition:
      //    event   -- jQuery event object
      //    action  -- string as insertion format identifier
      // Uses:
      //    UTIL.fullstop()
      //    GUIP.push.flip()
      //    TASK.fruit()
      // 2015-05-12 PerfektesChaos@de.wikipedia
      UTIL.fullstop( event );
      this.flip( 0 );
      TASK.fruit( action );
   };   // GUIP.push.fired()



   GUIP.push.flip = function ( action ) {
      // Create or hide result insertion push bar
      // Precondition:
      //    action  -- number: 0, 3, 4
      // Uses:
      //    this
      //    >  GUIP.$panel
      //    >  GUIP.push.$bibSection
      //    >< GUIP.push.$bar
      //    GUIP.input.fire()
      //    GUIP.push.$fiat()
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var btns, i;
      if ( action ) {
         GUIP.input.fire( true );
         if ( ! this.$bar ) {
            btns = [ "references", "bibSection", "refref", "compact" ];
            this.$bar = $( "<span>" );
            this.$bar.css( { "white-space": "nowrap" } );
            this.$bar.attr( { "id":  CITWT.type + "-push" } );
            for ( i = 0;  i < 4;  i++ ) {
               this.$fiat( btns[ i ] );
            }   // for i
            this.$references.css( { "margin-right": "3em" } );
            GUIP.$panel.append( this.$bar );
         }
         if ( action === 3 ) {
            this.$bibSection.hide();
         } else {
            this.$bibSection.show();
         }
         this.$bar.show();
      } else if ( this.$bar ) {
         this.$bar.hide();
      }
   };   // GUIP.push.flip()



   GUIP.push.bibSection = function ( event ) {
      // Button for insertion at bibSection has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    GUIP.push.fired()
      // Remark: Used as event handler -- 'this' is not GUIP.push
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.push.fired( event, "bibSection" );
   };   // GUIP.push.bibSection()



   GUIP.push.compact = function ( event ) {
      // Button for insertion at compact has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    GUIP.push.fired()
      // Remark: Used as event handler -- 'this' is not GUIP.push
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.push.fired( event, "compact" );
   };   // GUIP.push.compact()



   GUIP.push.references = function ( event ) {
      // Button for insertion at <references> has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    GUIP.push.fired()
      // Remark: Used as event handler -- 'this' is not GUIP.push
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.push.fired( event, "references" );
   };   // GUIP.push.references()



   GUIP.push.refref = function ( event ) {
      // Button for insertion at <ref></ref> has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    GUIP.push.fired()
      // Remark: Used as event handler -- 'this' is not GUIP.push
      // 2015-05-01 PerfektesChaos@de.wikipedia
      GUIP.push.fired( event, "refref" );
   };   // GUIP.push.refref()



//-----------------------------------------------------------------------



   I18N.texts = {
      bibSection:    { de: "== Literatur ==" },
      Citoid:        { en: "Citoid" },
      compact:       { en: "compact",
                       de: "kompakt" },
      gadgetHear:    { en: "_" },
      gadgetHelp:    { en: "?" },
      gadgetHide:    { en: "X" },
      gadgetHome:    { en: "0" },
      helpSite:      { en: "en",
                       de: "de" },
      invalid:       { en: "Invalid data:",
                       de: "Datenformat nicht erkannt" },
      msgEmpty:      { en: "???" },
      msgInvalid:    { en: "!?!?!" },
      msgTranslated: { en: "*" },
      msgUnanswered: { en: ":-(" },
      msgYippie:     { en: ":-)" },
      myButtons:     { en: "My buttons",
                       de: "Meine Buttons" },
      references:    { en: "&lt;references>" },
      refref:        { en: "&lt;ref>&lt;/ref>" },
      tipHear:       { en: "input field",
                       de: "Eingabefeld" },
      tipHelp:       { en: "help page",
                       de: "Hilfeseite" },
      tipHide:       { en: "hide box",
                       de: "Citoid-Box ausblenden" },
      tipHome:       { en: "abort requests, clear box",
                       de: "Anfragen abbrechen, Citoid-Box leeren" }
                };   // 2015-05-01 PerfektesChaos@de.wikipedia



   I18N.translate = { // fallback languages
                      "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" };



   I18N.facility = function ( available ) {
      // Localize in user language, GUI only
      // Precondition:
      //    available  -- translation object
      // Postcondition:
      //    Returns string, or undefined
      // Uses:
      //    >  I18N.userLang
      //    >  I18N.translate
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var slang = I18N.userLang,
          i, r;
      if ( typeof available[ slang ]  ===  "string" ) {
         r = available[ slang ];
      } else {
         i = slang.indexOf( "-", 2 );
         if ( i > 0 ) {
            slang = slang.substr( 0, i );
            if ( typeof available[ slang ]  ===  "string" ) {
               r = available[ slang ];
            }
         }
      }
      if ( ! r ) {
         if ( typeof I18N.translate[ slang ]  ===  "string" ) {
            slang = I18N.translate[ slang ];
            if ( typeof available[ slang ]  ===  "string" ) {
               r = available[ slang ];
            }
         }
         if ( ! r ) {
            r = available.en;
         }
      }
      return r;
   };   // I18N.facility()



   I18N.fair = function ( access, adapt, alter ) {
      // Localization
      // Precondition:
      //    access  -- configuration keyword
      //    adapt   -- false, if project (language) dependant
      //               else user language dependant
      //    alter   -- default string (en)
      // Postcondition:
      //    Returns value, or keyword 'access'
      // Uses:
      //    >  I18N.texts
      //    >  .core.site
      //    >  I18N.contentLang
      //    I18N.facility()
      // 2015-05-01 PerfektesChaos@de.wikipedia
      var el = I18N.texts[ access ],
          r  = ( alter ? alter : access ),
          say;
      if ( el ) {
         if ( adapt ) {
            say = I18N.facility( el );
            if ( say ) {
               r = say;
            } else if ( typeof alter  !==  "string" ) {
               r = "???" + access + "???";
            }
         } else {
            if ( typeof el[ CITWT.core.site ]  ===  "string" ) {
               r = el[ CITWT.core.site ];
            } else {
               if ( typeof el[ I18N.contentLang ]  ===  "string" ) {
                  r = el[ I18N.contentLang ];
               }
            }
         }
      } else if ( adapt ) {
         r = "???" + access + "???";
      }
      return r;
   };   // I18N.fair()



   I18N.fire = function () {
      // Initialize internationalization
      // Uses:
      //    >  .l10n
      //    >< I18N.texts
      //     < I18N.contentLang
      //     < I18N.wgUserLanguage
      //    mw.config.get()
      // 2015-05-31 PerfektesChaos@de.wikipedia
      var env = mw.config.get( [ "wgContentLanguage",
                                 "wgUserLanguage" ] );
      I18N.contentLang = env.wgContentLanguage;
      I18N.userLang    = env.wgUserLanguage.toLowerCase();
      if ( typeof CITWT.l10n  ===  "object"  &&  CITWT.l10n ) {
         $.extend( true, I18N.texts, CITWT.l10n );
      }
   };   // I18N.fire()



//-----------------------------------------------------------------------



   PREGO.feed = function () {
      // Provide PREGO
      // Uses:
      //    >  PREGO.signature
      //    >  PREGO.site
      //    >  PREGO.store
      //    >  PREGO.sub
      //    >  PREGO.maxage
      //    mw.loader.getState()
      //    mw.loader.state()
      //    REPOS.fire()
      // 2018-09-03 PerfektesChaos@de.wikipedia
      var sign = "ext.gadget." + PREGO.signature,
          rls;
      if ( ! mw.loader.getState( sign ) ) {
         rls = { };
         rls[ sign ] = "loading";
         mw.loader.state( rls );
         REPOS.fire( PREGO.site,
                     PREGO.store + PREGO.signature + PREGO.sub,
                     false,
                     { action: "raw",
                       ctype:  "text/javascript",
                       bcache: 1,
                       maxage: PREGO.maxage } );
      }
   };   // PREGO.feed()



//-----------------------------------------------------------------------



   REPOS.fire = function ( at, access, append, alter ) {
      // Load from external URL
      // Precondition:
      //    at      -- Wikimedia Foundation site code, or not
      //    access  -- string with basic page name
      //    append  -- string with subpage, or not
      //    alter   -- parameter object, or MIME string, or not
      // Uses:
      //    >< REPOS.requests
      //    REPOS.foundation()
      //    mw.loader.load()
      // 2018-03-21 PerfektesChaos@de.wikipedia
      var source, syntax;
      if ( typeof REPOS.requests  !==  "object" ) {
         REPOS.requests = { };
      }
      if ( typeof REPOS.requests[ access ]  !==  "boolean" ) {
         REPOS.requests[ access ] = true;
         if ( append ) {
            source = access + append;
         } else {
            source = access;
         }
         if ( at ) {
            source = REPOS.foundation( at, source, alter );
            if ( typeof alter  ===  "object"
                 &&     alter   &&
                 typeof alter.ctype  ===  "string" ) {
               syntax = alter.ctype;
            }
         } else {
            syntax = alter;
         }
         mw.loader.load( source, syntax );
      }
   };   // REPOS.fire()



   REPOS.foundation = function ( at, access, alter ) {
      // Create URL within Wikimedia Foundation
      // Precondition:
      //    at      -- site code, or not
      //    access  -- string with page name
      //    alter   -- parameter object, or not
      // Postcondition:
      //    Returns full URL
      // 2018-03-21 PerfektesChaos@de.wikipedia
      var s = access,
          r = encodeURI( s );
      if ( typeof alter  ===  "object"
           &&     alter ) {
         r = "/w/index.php?title=" + r;
         if ( access.substr( -3 ) === ".js" ) {
            alter.ctype = "text/javascript";
         } else if ( access.substr( -4 ) === ".css" ) {
            alter.ctype = "text/css";
         }
         alter.action = "raw";
         for ( s in alter ) {
            r = r + "&" + s + "=" + encodeURI( alter[ s ] );
         }   // for s in alter
      } else {
         r = "/wiki/" + r;
      }
      if ( typeof at  ===  "string"
           &&     at ) {
         switch ( at ) {
            case "meta":
               r = "meta.wikimedia.org" + r;
               break;
            case "mw":
               r = "www.mediawiki.org" + r;
               break;
            case "w:en":
               r = "en.wikipedia.org" + r;
               break;
            default:
               r = window.location.host + r;
         }   // switch at
         r = "https://" + r;
      }
      return r;
   };   // REPOS.foundation()



//-----------------------------------------------------------------------



   TASK.fiat = function ( event ) {
      // Panel launch button has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //     < TASK.template
      //    GUIP.input.filler()
      //    UTIL.fullstop()
      //    GUIP.input.fire()
      //    TASK.found()
      //    GUIP.caret.fetch()
      //    GUIP.flat()
      //    (TASK.found)
      // Remark: Used as event handler -- 'this' is not TASK
      // 2015-06-16 PerfektesChaos@de.wikipedia
      var listen = true,
          suggest;
      if ( event ) {
         listen = GUIP.live  &&  GUIP.$button.is( ":focus" );
         if ( listen ) {
            UTIL.fullstop( event );
         }
      }
      if ( listen ) {
         suggest = GUIP.input.filler();
         TASK.template = false;
         if ( suggest ) {
            TASK.found( suggest );
         } else {
            GUIP.flat( true );
            GUIP.caret.fetch( TASK.found );
         }
      }
   };   // TASK.fiat()



   TASK.filter = function () {
      // Process completed result of queries
      // Uses:
      //    >  TASK.query
      //    >  APIC.queries
      //    >  PROJ
      //    >  .bibRecord
      //     < TASK.template
      //    .opus.fire()
      //    .(project).fire()
      //    GUIP.caret.fill()
      //    GUIP.flat()
      //    GUIP.push.flip()
      // Remark: Used as event handler -- 'this' is not TASK
      // 2017-06-04 PerfektesChaos@de.wikipedia
      var mode, result;
      if ( ! APIC.missing ) {
         result = CITWT.opus.fire( TASK.query, APIC.queries );
      }
      TASK.template = PROJ.fire( result );
      switch ( typeof TASK.template ) {
         case "object":
            mode = ( TASK.template[ 0 ][ 2 ] ? 4 : 3 );
            break;
         case "string":
            if ( CITWT.bibRecord  &&  CITWT.bibRecord.launch ) {
               GUIP.caret.fill( TASK.template );
               TASK.template = false;
               GUIP.flat( "translated", TASK.query.$links );
            } else {
               mode = 3;
            }
            break;
         default:
            TASK.template = false;
      }   // switch typeof array
      if ( mode ) {
         GUIP.push.flip( mode );
      }
   };   // TASK.filter()



   TASK.fire = function () {
      // Initialize page
      // Precondition:
      //    mediawiki modules and user options/config have been loaded
      // Uses:
      //    >  .core.site
      //    >  mw.user.options
      //     < PROJ
      //     < GUIP.opts
      //    I18N.fire()
      //    mw.user.options.get()
      //    mw.loader.using()
      //    BOOK.fire()
      //    (BOOK.fire)
      // 2016-03-21 PerfektesChaos@de.wikipedia
      if ( typeof CITWT[ CITWT.core.site ]  ===  "object" ) {
         PROJ = CITWT[ CITWT.core.site ];
         if ( PROJ ) {
            I18N.fire();
            GUIP.opts = mw.user.options.get( [ "showtoolbar",
                                               "usebetatoolbar" ] );
            if ( GUIP.opts.usebetatoolbar ) {
               // race condition
               mw.loader.using( [ "ext.wikiEditor" ],
                                BOOK.fire );
            } else {
               BOOK.fire();
            }
         }
      }
   };   // TASK.fire()



   TASK.follow = function () {
      // Proceed after completion of query results
      // Uses:
      //    >  .signature
      //    >  TASK.query.$links
      //    GUIP.flat()
      //    mw.loader.using()
      //    (TASK.filter)
      // 2016-02-21 PerfektesChaos@de.wikipedia
      GUIP.flat( "translated" );
      mw.loader.using( [ CITWT.signature + "/opus",
                         "user:PerfektesChaos/WikiSyntaxTextMod/U" ],
                       TASK.filter );
      GUIP.flat( "translated", TASK.query.$links );
   };   // TASK.follow()



   TASK.found = function ( ask ) {
      // Source text selection is ready or input is known
      // Precondition:
      //    ask  -- search string, if known
      // Uses:
      //    >  GUIP.caret.selection
      //    >  .bibRecord
      //    >  APIC.stateless
      //    >  APIC.found
      //     < TASK.query
      //     < APIC.queries
      //    DESC.fire()
      //    GUIP.flat()
      //    APIC.worldCat.fire()
      //    .(project).favorite()
      //    GUIP.$flip()
      //    GUIP.$flipper()
      //    mw.loader.using()
      //    GUIP.flop()
      //    GUIP.input.filler()
      //    (APIC.fire)
      // 2017-06-11 PerfektesChaos@de.wikipedia
      var search    = ( ask ? ask : GUIP.caret.selection ),
          stateless = "|isbn|",    // |oclc
          i, k, m, project, q, suggest, url;
      if ( ! search   &&   CITWT.bibRecord ) {
         search = CITWT.bibRecord.sign + " " + CITWT.bibRecord.subject;
      }
      if ( search ) {
         TASK.query = DESC.fire( search );
         if ( TASK.query ) {
            q = TASK.query;
            switch ( typeof q.url ) {
               case "string":
                  q.url = [ q.url ];
                  break;
               case "object":
                  break;
               default:
                  q.url = false;
            }   // switch typeof q.url
            if ( q.url ) {
               GUIP.flat( "throbber" );
               APIC.queries = [ ];
               q.$links     = false;
               for ( i = 0;  i < q.url.length;  i++ ) {
                  url = q.url[ i ];
                  for ( m = 0;  m < i;  m++ ) {
                     if ( url === q.url[ m ] ) {
                        url = false;
                        break;
                     }
                  }   // for m
                  if ( typeof url  ===  "string" ) {
                     k = i + 1;
                     APIC.queries.push( url );
                     url        = [ url,  "[" + k + "]" ];
                     q.url[ i ] = url;
                  }
                  if ( url ) {
                     q.$links = GUIP.$flip( q.$links, url, k );
                  }
               }   // for i
               if ( typeof q.say  ===  "string" ) {
                  GUIP.$flipper( q.$links, q.say );
                  suggest = q.say;
               }
               GUIP.flat( "throbber", q.$links );
               if ( typeof APIC.stateless  ===  "string"   &&
                    typeof q.scheme  ===  "string"   &&
                    stateless.indexOf( "|" + q.scheme + "|" )  >=  0 ) {
                  stateless = q[ q.scheme ];
                  if ( typeof stateless  ===  "string" ) {
                     stateless = q.scheme.toUpperCase()
                                 +  " " + stateless;
                     APIC.queries.push( stateless );
                  }
               }
               if ( typeof PROJ.favorite  ===  "function" ) {
                  project = PROJ.favorite( q, APIC.found );
                  if ( project ) {
                     project.local = true;
                     APIC.queries.push( project );
                  }
               }
               mw.loader.using( [ "mediawiki.api" ],
                                APIC.fire );
               if ( typeof q.source  ===  "string" ) {
                  suggest = q.source;
               }
               GUIP.input.filler( suggest );
            } else {
               GUIP.flop( q );
            }
         } else {
            GUIP.flat( "empty" );
         }
      } else {
         TASK.query = false;
      }
   };   // TASK.found()



   TASK.fruit = function ( action ) {
      // Insert template
      // Precondition:
      //    action  -- string as insertion format identifier
      // Uses:
      //    >< TASK.template
      //    .opus.fruit()
      //    GUIP.caret.fill()
      // 2015-05-25 PerfektesChaos@de.wikipedia
      var indent, seed, start, suffix;
      switch ( action ) {
         case "compact":
            indent = -1;
            break;
         case "refref":
            indent = -1;
            start  = "<ref>";
            suffix = "</ref>";
            break;
         case "bibSection":
            indent = 3;
            start  = "\n* ";
            suffix = "\n";
            break;
         case "references":
            indent = 1;
            start  = "\n<ref name=\"\">\n";
            suffix = "\n</ref>\n";
            break;
      }   // switch action
      seed = CITWT.opus.fruit( TASK.template, indent );
      TASK.template = false;
      if ( seed ) {
         seed = ( start ? start : "" )  +
                seed  +
                ( suffix ? suffix : "" );
         GUIP.caret.fill( seed );
      }
   };   // TASK.fruit()



//-----------------------------------------------------------------------



   UTIL.feed = function ( assigned, access, address ) {
      // Load and run script
      // Precondition:
      //    assigned  -- signature
      //    access    -- script
      //    address   -- URL to be used, else created from access
      // Uses:
      //    >  .suite
      //    >  REPOS.maxage
      //    mw.loader.getState()
      //    mw.loader.state()
      //    mw.loader.load()
      //    REPOS.fire()
      // 2018-09-03 PerfektesChaos@de.wikipedia
      var p, rls;
      if ( ! mw.loader.getState( assigned ) ) {
         rls = { };
         rls[ assigned ] = "loading";
         mw.loader.state( rls );
         if ( address ) {
            mw.loader.load( address );
         } else {
            p = { bcache: 1 };
            p.maxage = ( CITWT.suite === "r"  ?  REPOS.maxage  :  100 );
            REPOS.fire( REPOS.site,
                        REPOS.store + access + ".js",
                        false,
                        p );
         }
      }
   };   // UTIL.feed()



   UTIL.fetch = function ( access ) {
      // Run subscripts
      // Precondition:
      //    access  -- subscript
      // Uses:
      //    >  .signature
      //    >  .type
      //    >  .suite
      //    UTIL.feed()
      // 2018-07-28 PerfektesChaos@de.wikipedia
      var sub = "/" + access;
      UTIL.feed( CITWT.signature + sub,
                 CITWT.type + sub + "/" + CITWT.suite );
   };   // UTIL.fetch()



   UTIL.friend = function () {
      // Load current project plug-in, if appropriate
      // Postcondition:
      //    Returns something, if plug-in should become available
      // Uses:
      //    >  .signature
      //    >  .core.site
      //    >  .core.sites
      //    UTIL.feed()
      //    mw.loader.getState()
      //    UTIL.fetch()
      //    console.log()
      // 2018-07-14 PerfektesChaos@de.wikipedia
      var signature = CITWT.signature + "/" + CITWT.core.site,
          r, s;
      switch ( typeof CITWT[ CITWT.core.site ] ) {
         case "string":
            // load from URL
            r = CITWT[ CITWT.core.site ];
            if ( r ) {
               UTIL.feed( signature, false, r );
            }
            break;
         case "object":
            // already provided
            if ( mw.loader.getState( signature ) ) {
               r = CITWT[ CITWT.core.site ];
            }
            break;
         default:
            // load from code base, if registered
            s = "|" + CITWT.core.site + "|";
            if ( CITWT.core.sites.indexOf( s )  >=  0 ) {
               UTIL.fetch( CITWT.core.site );
               r = true;
            }
      }   // switch typeof CITWT[ CITWT.core.site ]
      if ( ! r   &&
           typeof window.console  ===  "object"   &&
           typeof window.console.log  ===  "function" ) {
         window.console.log( CITWT.type + " * Local project unknown: "
                             + CITWT.core.site );
      }
      return r;
   };   // UTIL.friend()



   UTIL.fullstop = function ( activity ) {
      // Stop immediately event propagation
      // Precondition:
      //    activity  -- event
      // 2015-05-12 PerfektesChaos@de.wikipedia
      if ( typeof activity  ===  "object"   &&   activity ) {
         if ( typeof activity.preventDefault  ===  "function" ) {
            activity.preventDefault();
         }
         if ( typeof activity.stopPropagation  ===  "function" ) {
            activity.stopPropagation();
         }
      }
  };   // UTIL.fullstop()



//-----------------------------------------------------------------------



   XPORT.core.fold = function ( analyse ) {
      // Derive simple template parameters; not nested
      // Precondition:
      //    analyse  -- string, with parameter list
      // Postcondition:
      //    Returns object, with components identified by strings
      // 2015-05-12 PerfektesChaos@de.wikipedia
      var i = 0,
          m = 0,
          r = { },
          s = analyse,
          t = [ ],
          j, k, sign;
      while ( s ) {
         j = s.indexOf( "|", i );
         k = s.indexOf( "}}", i );
         if ( j >= 0  &&  j < k ) {
            if ( i ) {
               t.push( s.substring( i, j ) );
            }
            i = j + 1;
         } else if ( k >= 0 ) {
            if ( k ) {
               t.push( s.substring( i, k ) );
            }
            s = false;
         } else {
            s = false;
         }
      }   // while s
      for ( i = 0;  i < t.length;  i++ ) {
         s = t[ i ].replace( /^\s+/, "" )
                   .replace( /\s+$/, "" );
         j = s.indexOf( "=", i );
         if ( j > 0 ) {
            sign = s.substr( 0, j )
                    .replace( /\s+$/, "" );
            s    = s.substr( j + 1 )
                    .replace( /^\s+/, "" );
         } else {
            m++;
            sign = "" + m;
         }
         r[ sign ] = s;
      }   // for i
      return r;
   };   // XPORT.core.fold()



//-----------------------------------------------------------------------



   XPORT.fire = function ( event ) {
      // Tool button has been clicked
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    >  GUIP.$panel
      //    UTIL.fullstop()
      //    GUIP.flat()
      //    GUIP.factory()
      //    TASK.fiat()
      // Remark: Used as event handler -- 'this' is not XPORT
      // 2015-05-23 PerfektesChaos@de.wikipedia
      UTIL.fullstop( event );
      if ( GUIP.$panel ) {
         GUIP.flat( false );
      } else {
         GUIP.factory();
      }
      TASK.fiat();
   };   // XPORT.fire()



//-----------------------------------------------------------------------



   function fire() {
      // Start regular workflow in edit mode, if appropriate
      // Uses:
      //    >  CITWT
      //    >  HELP.site
      //    >  HELP.support
      //    >  Version
      //    >  .signature
      //    >< .bibRecord
      //     < .support
      //     < .core.site
      //    facilitated()
      //    mw.config.get()
      //    PREGO.feed()
      //    UTIL.fetch()
      //    UTIL.feed()
      //    UTIL.friend()
      //    mw.loader.using()
      //    (TASK.fire)
      // 2018-07-28 PerfektesChaos@de.wikipedia
      var env, sub, suitable;
      facilitated();
      if ( CITWT ) {
         env  = mw.config.get( [ "wgAction",
                                 "wgDBname",
                                 "wgIsProbablyEditable",
                                 "wgPageContentModel" ] );
         CITWT.support   = "[[" + HELP.site + ":" + HELP.support + "]]";
         CITWT.core.site = env.wgDBname;
         suitable        = "|edit|submit|";
         if ( suitable.indexOf( env.wgAction )  >  0   &&
              env.wgIsProbablyEditable   &&
              env.wgPageContentModel === "wikitext"   &&
              UTIL.friend() ) {
            PREGO.feed();
            UTIL.fetch( "opus" );
            sub = ( Version > 1  ?  "r"  :  "d" );
            UTIL.feed( "user:PerfektesChaos/WikiSyntaxTextMod/S",
                       "WikiSyntaxTextMod/" + sub + "S" );
            UTIL.feed( "user:PerfektesChaos/WikiSyntaxTextMod/U",
                       "WikiSyntaxTextMod/" + sub + "U" );
            UTIL.feed( "ext.gadget.isbnLib",
                       "isbnLib/" + sub );
            if ( typeof CITWT.bibRecord  !==  "object" ) {
               CITWT.bibRecord = false;
            }
            mw.loader.using( [ "mediawiki.user",
                               "mediawiki.util",
                               "user",
                               "user.options",
                               CITWT.signature + "/" + env.wgDBname ],
                             TASK.fire );
         }
      }
   }   // fire()



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

/// EOF </nowiki>   citoidWikitext/core/d.js