User:PerfektesChaos/js/jsonDebug/d.js

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/jsonDebug/d.js
//  Show JSON errors analysis live
/// 2018-08-24 PerfektesChaos@de.wikipedia
//  ResourceLoader:  compatible;
//    dependencies: user, mw.API
/// Fingerprint: #0#0#
/// @license GPL [//www.mediawiki.org/w/COPYING] (+GFDL, LGPL, CC-BY-SA)
/// <nowiki>
/* global window: 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   = -1.2,
       Signature = "jsonDebug",
       JSOND = { site:    "w:en",
                 store:   "User:PerfektesChaos/js/" + Signature,
                 sel:     "json-code-lint",
                 src:     "div,pre",
                 using:   null },
/*
       PREGO = { app:       false,
                 signature: "preferencesGadgetOptions",
                 site:      "w:en",
                 store:     "User:PerfektesChaos/js/" },
*/
       REPOS = { requests: false },
       UI    = { bad:      { "background-color": "#F0F080",
                             "border":           "#FF0000 1px solid",
                             "color":            "#FF0000",
                             "font-family":      "monospace",
                             "font-size":        "80%",
                             "font-weight":      "normal",
                             "padding":          "0.3em" },
                 code:     { "margin-top":       "1em",
                             "max-width":        "95%",
                             "width":            "95%" },
                 css:      { "background-color": "#D0D0D0",
                             "border":           "#808080 1px solid",
                             "clear":            "both",
                             "margin-bottom":    "1rem",
                             "max-width":        "95%",
                             "padding":          "1em" },
                 normal:   10,
                 $button:  false },
       VALID = { jsonlint:  false,
                 reLineNum: "line ([0-9]+)" },
       ZEBRA = { codemirror:  false };



/*
   function features( apply ) {
      // Config hook has been fired
      // Precondition:
      //    apply  -- hook payload
      // Uses:
      // 2018-03-01 PerfektesChaos@de.wikipedia
      if ( typeof apply  ===  "object"
           &&     apply ) {
      }
   }   // features()
*/



   JSOND.feed = function ( ask ) {
      // External access interface
      // Precondition:
      //    ask  -- string with JSON source code
      // Uses:
      //    >  JSOND.using
      //     < JSOND.source
      //    mw.loader.using()
      //    JSOND.feeder()
      //    (JSOND.feeder)
      // 2018-03-01 PerfektesChaos@de.wikipedia
      var s;
      if ( typeof ask  ===  "string" ) {
         s = ask.trim();
         if ( s ) {
            JSOND.source = s;
            if ( JSOND.using ) {
               mw.loader.using( JSOND.using, JSOND.feeder );
            } else {
               JSOND.feeder();
            }
         }
      }
   };   // JSOND.feed()



   JSOND.feeder = function () {
      // External access, ensure DOM
      // Precondition:
      //    Standard modules have been loaded
      // Uses:
      //    (JSOND.feeding)
      // 2018-03-01 PerfektesChaos@de.wikipedia
      $( JSOND.feeding );
   };   // JSOND.feeder()



   JSOND.feeding = function () {
      // Perform external access
      // Precondition:
      //    DOM ready
      //    Standard modules have been loaded
      // Uses:
      //    >  JSOND.source
      //    JSOND.flip(JSOND.flip)
      // 2018-03-01 PerfektesChaos@de.wikipedia
      VALID.fire( JSOND.source );
      JSOND.flip();
   };   // JSOND.feeding()



   JSOND.fire = function () {
      // Start execution
      // Precondition:
      //    all  -- true, if entire content is JSON
      //    Standard modules have been loaded
      // Uses:
      //    >  Signature
      //     < JSOND.support
      //    mw.hook()
      //    PREGO.feed()
      //    (JSOND.fresh)
      // 2018-03-18 PerfektesChaos@de.wikipedia
/*
      PREGO.feed();
      mw.hook( Signature + ".config" ).add( JSOND.features );
*/
      JSOND.support = Signature + "-button";
      mw.hook( "wikipage.content" ).add( JSOND.fresh );
   };   // JSOND.fire()



   JSOND.fired = function ( event ) {
      // Button has been pressed
      // Precondition:
      //    event  -- jQuery event object
      // Uses:
      //    >  JSOND.large
      //    >  UI.live
      //    >  UI.$source
      //    >  JSOND.sel
      //    >  JSOND.src
      //     < JSOND.source
      //    JSOND.flip()
      //    VALID.fire()
      // 2018-03-18 PerfektesChaos@de.wikipedia
      var $button, $json;
      event.stopPropagation();
      JSOND.flip();
      JSOND.source = false;
      if ( JSOND.large ) {
         if ( UI.live ) {
            JSOND.source = UI.$source.val();
         } else {
            JSOND.source = UI.$source.text();
         }
      } else {
         $button = $( event.target );
         $json   = $button.next();
         $button.attr( { disabled: true } );
         if ( $json.hasClass( JSOND.sel )   &&
              $json.filter( JSOND.src ) ) {
            JSOND.source = $json.text();
         }
      }
      if ( JSOND.source ) {
         JSOND.source = JSOND.source.trim();
         VALID.fire( JSOND.source );
      }
   };   // JSOND.fired()



   JSOND.first = function () {
      // Autorun on loading
      // Uses:
      //    >  Signature
      //    >  JSOND.using
      //    >  JSOND.site
      //    >  JSOND.store
      //    >  Version
      //     < JSOND.signature
      //     < JSOND.large
      //    mw.loader.getState()
      //    mw.loader.state()
      //    mw.config.get()
      //    mw.loader.using()
      //    JSOND.fire()
      //    mw.hook()
      //    (JSOND.fire)
      //    (JSOND.feed)
      // 2018-08-24 PerfektesChaos@de.wikipedia
      var env, pub, rls;
      JSOND.signature = "ext.gadget." + Signature;
      if ( mw.loader.getState( JSOND.signature )  !==  "ready" ) {
         env = mw.config.get( [ "wgAction",
                                "wgPageContentModel" ] );
         rls = { };
         rls[ JSOND.signature ] = "ready";
         mw.loader.state( rls );
         JSOND.large =
                 ( env.wgPageContentModel.toLowerCase().indexOf( "json" )
                   >=  0 );
         switch ( env.wgAction ) {
            case "view":
            case "edit":
            case "submit":
            case "parsermigration-edit":
               if ( JSOND.using ) {
                  mw.loader.using( JSOND.using, JSOND.fire );
               } else {
                  JSOND.fire();
               }
               break;
         }   // switch wgAction
         pub = { doc:  "[[" + JSOND.site + ":" + JSOND.store + "]]",
                 feed: JSOND.feed,
                 type: Signature,
                 vsn:  Version };
         mw.hook( Signature + ".ready" ).fire( pub );
      }
   };   // JSOND.first()



   JSOND.flip = function () {
      // Enable all buttons
      // Uses:
      //    >  UI.$area
      //    >  JSOND.support
      // 2018-03-01 PerfektesChaos@de.wikipedia
      UI.$area.find( "." + JSOND.support ).attr( { disabled: false } );
   };   // JSOND.flip()



   JSOND.fresh = function ( $area ) {
      // Handler when content area has changed
      // Precondition:
      //    $area  -- jQuery element of content area
      //    (DOM ready)
      // Uses:
      //    >  JSOND.large
      //    >  JSOND.sel
      //    >  JSOND.src
      //    >< UI.$button
      //     < UI.$area
      //     < UI.live
      //     < UI.$source
      //    JSOND.furnish()
      //    (JSOND.furnish)
      // 2018-03-18 PerfektesChaos@de.wikipedia
      var $e;
      UI.$area = $area;
      if ( JSOND.large ) {
         $e      = UI.$area.find( "#wpTextbox1" );
         UI.live = $e.length;
         if ( UI.live ) {
            UI.$source = $e;
         } else {
            UI.$source = false;
            $e = UI.$area.children();   // <div dir="ltr">
            if ( $e.length === 1 ) {
               $e = $e.children();
               if ( $e.length === 1  &&
                    $e.hasClass( "mw-highlight" ) ) {
                  $e = $e.children();    // <pre>
                  if ( $e.length === 1  &&
                       ! $e.children().length  &&
                       $e.filter( "pre" ).length ) {
                     UI.$source = $e;
                  }
               }
            }
         }
         if ( UI.$source  &&  ! JSOND.$button ) {
            UI.$button = JSOND.furnish( 0, $area.get( 0 ) );
         } else if ( JSOND.$button ) {
            JSOND.$button.remove();
            JSOND.$button = false;
         }
      } else {
         UI.$area.find( "." + JSOND.support ).remove();
         UI.$area.find( "." + JSOND.sel )
                 .filter( JSOND.src )
                 .each( JSOND.furnish );
      }
   };   // JSOND.fresh()



   JSOND.furnish = function ( at, area ) {
      // Create button before JSON content
      // Precondition:
      //    at    -- sequence number
      //    area  -- DOM element of content
      // Postcondition:
      //    Returns jQuery <button>
      // Uses:
      //    >  Signature
      //    >  JSOND.support
      //    (JSOND.fired)
      // 2018-03-18 PerfektesChaos@de.wikipedia
      var $r = $( "<button>" ).addClass( JSOND.support )
                              .click( JSOND.fired )
                              .css( { "margin-left":  "0.5em",
                                      "margin-right": "0.5em" } )
                              .text( Signature );
      $( area ).before( $r );
      return $r;
   };   // JSOND.furnish()



/*
   PREGO.feed = function () {
      // Provide PREGO
      // Precondition:
      //    mediawiki.Title has been loaded
      // Uses:
      //    >  PREGO.signature
      //    >  PREGO.site
      //    >  PREGO.store
      //    mw.loader.getState()
      //    REPOS.fire()
      // 2018-03-01 PerfektesChaos@de.wikipedia
      var s = "ext.gadget." + PREGO.signature;
      if ( mw.loader.getState( s )  !==  "ready" ) {
         REPOS.fire( PREGO.site,
                     PREGO.store + "/" + PREGO.signature,
                     "/r.js",
                     { action: "raw",
                       ctype:  "text/javascript",
                       bcache: 1,
                       maxage: 604813 } );
      }
   };   // 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()



   UI.factory = function () {
      // Equip page with box
      // Uses:
      //    >  UI.signature
      //    >  UI.css
      //    >  JSOND.store
      //    >  JSOND.site
      //    >  Signature
      //    >  Version
      //    >  UI.bad
      //    >  UI.$area
      //     < UI.$wrapper
      //     < UI.$warn
      //     < UI.$code
      //    REPOS.foundation()
      //    (UI.flip)
      // 2018-03-21 PerfektesChaos@de.wikipedia
      var ltr   = ( $( "html" ).attr( "dir" )  !==  "rtl" ),
          $head = $( "<div>" ),
          $e;
      UI.$wrapper = $( "<div>" ).addClass( UI.signature );
      UI.$wrapper.css( { "clear":       "both",
                         "font-size":   "1rem",
                         "font-style":  "normal",
                         "font-weight": "normal",
                         "max-width":   "100%",
                         "width":       "100%" } );
      if ( typeof UI.css  ===  "object"
           &&     UI.css ) {
         UI.$wrapper.css( UI.css );
      }
      if ( typeof JSOND.store  ===  "string"
           &&     JSOND.store ) {
         $e = $( "<a>" ).attr( { href:   REPOS.foundation( JSOND.site,
                                                           JSOND.store ),
                                 target: Signature,
                                 title:  "Tool info / doc" } );
      } else {
         $e = $( "<span>" );
      }
      $e.css( { "font-family": "monospace",
                "font-size":   "1.6em",
                "font-weight": "bold" } )
        .text( Signature );
      $head.append( $e );
      $e = $( "<span>" ).css( { "font-size":    "smaller",
                                "margin-left":  "2em",
                                "margin-right": "2em" } )
                        .text( Version + "" );
      $head.append( $e )
           .css( { "float": ( ltr ? "left": "right" ) } );
      UI.$wrapper.append( $head );
      $e = $( "<button>" ).click( UI.flip )
                          .css( { "color":       "#FF0000",
                                  "float":       ( ltr ? "right":
                                                         "left" ),
                                  "font-weight": "bold",
                                  "padding":     "0.2em" } )
                          .text( "X" );
      UI.$wrapper.append( $e );
      UI.$area.prepend( UI.$wrapper );
      $e = $( "<div>" ).css( { "clear": "both" } );
      UI.$wrapper.append( $e );
      UI.$warn = $( "<pre>" ).addClass( Signature + "-warn" )
                             .css( { "clear":    "both",
                                     "overflow": "auto" } )
                             .hide();
      if ( typeof UI.bad  ===  "object"
           &&     UI.bad ) {
         UI.$warn.css( UI.bad );
      }
      UI.$wrapper.append( UI.$warn );
      UI.$code = $( "<textarea>" ).addClass( Signature + "-code" )
                                  .attr( { disabled: true } )
                                  .css( { "overflow": "auto" } )
                                  .hide();
      if ( typeof UI.code  ===  "object"
           &&     UI.code ) {
         UI.$code.css( UI.code );
      }
      UI.$wrapper.append( UI.$code );
   };   // UI.factory()



   UI.fire = function ( apply, alert ) {
      // Start presentation
      // Precondition:
      //    apply  -- string with JSON source code, or false
      //    alert  -- string with error report, or false
      // Uses:
      //    >  Signature
      //    >  UI.$warn
      //    >  UI.$code
      //    >  UI.$area
      //    >  UI.normal
      //    >< VALID.reLineNum
      //     < UI.signature
      //     < UI.$wrapper
      //    UI.factory()
      //    ZEBRA.fire()
      // 2018-03-01 PerfektesChaos@de.wikipedia
      var got, k, n;
      UI.signature = Signature + "-wrapper";
      UI.$wrapper  = UI.$area.find( "." + UI.signature );
      if ( ! UI.$wrapper.length ) {
         UI.factory();
      }
      if ( ! apply  || ! alert ) {
         UI.$warn.hide();
         if ( ! apply ) {
            UI.$code.hide();
         }
      }
      UI.$area.scrollTop( 0 );
      UI.$wrapper.show();
      if ( apply ) {
         if ( alert ) {
            UI.$warn.show()
                    .text( alert );
            if ( typeof VALID.reLineNum  ===  "string" ) {
               VALID.reLineNum = new RegExp( VALID.reLineNum );
            }
            if ( typeof VALID.reLineNum  ===  "object" ) {
               got = alert.match( VALID.reLineNum );
               if ( got ) {
                  k = parseInt( got[ 1 ], 10 );
                  if ( k > 1 ) {
                     k--;
                  }
               }
            }
         }
         n = apply.split( "\n" ).length;
         if ( n > UI.normal ) {
            n = UI.normal;
         }
         UI.$code.attr( "rows", n )
                 .show()
                 .val( apply );
         ZEBRA.fire( apply, k );
      }
   };   // UI.fire()



   UI.flip = function () {
      // Hide box
      // Uses:
      //    >  UI.$wrapper
      //    JSOND.flip()
      // 2018-03-01 PerfektesChaos@de.wikipedia
      UI.$wrapper.hide();
      JSOND.flip();
   };   // UI.flip()



   VALID.fiat = function () {
      // Execute validation
      // Precondition:
      //    jsonlint has been loaded
      // Uses:
      //    >  VALID.jsonlint
      //    >  VALID.source
      //     < VALID.scream
      //    VALID.jsonlint.parse()
      //    UI.fire()
      // 2018-03-01 PerfektesChaos@de.wikipedia
      VALID.scream = false;
      try {
         VALID.jsonlint.parse( VALID.source );
      } catch ( e ) {
         VALID.scream = e.message;
      }
      UI.fire( VALID.source, VALID.scream );
   };   // VALID.fiat()



   VALID.fire = function ( apply ) {
      // Start validation and presentation
      // Precondition:
      //    apply  -- string with JSON source code
      //    DOM ready
      //    mediawiki.Title has been loaded
      // Uses:
      //    >  JSOND.site
      //    >  JSOND.store
      //    >  Signature
      //    >< VALID.jsonlint
      //     < VALID.source
      //    UI.fire()
      //    ZEBRA.first()
      //    VALID.fiat()
      //    REPOS.fire()
      //    mw.hook()
      //    (VALID.furnish)
      // 2018-03-21 PerfektesChaos@de.wikipedia
      UI.fire();
      ZEBRA.first();
      VALID.source = apply;
      if ( VALID.jsonlint ) {
         VALID.fiat();
      } else if ( VALID.jsonlint === false ) {
         VALID.jsonlint = null;
         REPOS.fire( JSOND.site,
                     JSOND.store + "/jsonlint.js",
                     false,
                     { action: "raw",
                       ctype:  "text/javascript",
                       bcache: 1,
                       maxage: 604800 } );
         mw.hook( Signature + ".jsonlint" ).add( VALID.furnish );
      }
   };   // VALID.fire()



   VALID.furnish = function ( application ) {
      // jsonlint has been loaded
      // Precondition:
      //    application  -- jsonlint object
      // Uses:
      //     < VALID.jsonlint
      //    VALID.fiat()
      // 2018-03-01 PerfektesChaos@de.wikipedia
      if ( application  &&
           typeof application  ===  "object"  &&
           typeof application.parse  ===  "function" ) {
         VALID.jsonlint = application;
         VALID.fiat();
      }
   };   // VALID.furnish()



   ZEBRA.config = { autofocus:   true,
                    gutters:     [ "CodeMirror-linenumbers" ],
                    lineNumbers: true,
                    mode:        "javascript",
                    readOnly:    true,
                    tabSize:     2,
                    theme :      Signature };



   ZEBRA.fiat = function ( apply ) {
      // Execute enhanced presentation
      // Precondition:
      //    apply  -- mode function for javascript, if not yet defined
      //    CodeMirror ready and initialized
      // Uses:
      //    >  ZEBRA.codemirror
      //    >  UI.$code
      //    >  ZEBRA.config
      //    >  ZEBRA.source
      //    >  ZEBRA.invalid
      //    >< ZEBRA.editor
      // 2018-03-01 PerfektesChaos@de.wikipedia
      if ( typeof apply  ===  "object"
           &&     apply   &&
           typeof apply.mode  ===  "function" ) {
         ZEBRA.codemirror.defineMode( "javascript", apply.mode );
      }
      if ( ZEBRA.editor ) {
         ZEBRA.editor.toTextArea();
      }
      ZEBRA.editor = ZEBRA.codemirror.fromTextArea( UI.$code.get( 0 ),
                                                    ZEBRA.config );
      ZEBRA.editor.setSize( null, UI.$code.height() );
      ZEBRA.editor.setValue( ZEBRA.source );
      if ( ZEBRA.invalid ) {
         ZEBRA.focus( ZEBRA.invalid );
      }
   };   // ZEBRA.fiat()



   ZEBRA.fire = function ( apply, at ) {
      // Request presentation
      // Precondition:
      //    apply  -- string with JSON source code
      //    at     -- line number with error, or false
      // Uses:
      //    >  ZEBRA.codemirror
      //     < ZEBRA.source
      //     < ZEBRA.invalid
      //    mw.loader.using()
      //    ZEBRA.fiat()
      //    (ZEBRA.furnish)
      // 2018-03-01 PerfektesChaos@de.wikipedia
      ZEBRA.source  = apply;
      ZEBRA.invalid = at || false;
      if ( ZEBRA.codemirror ) {
         ZEBRA.fiat();
      } else {
         mw.loader.using( [ "ext.CodeMirror" ],
                          ZEBRA.furnish );
      }
   };   // ZEBRA.fire()



   ZEBRA.first = function () {
      // Pre-emptive loading of CodeMirror data
      // Uses:
      //    >  JSOND.site
      //    >  JSOND.store
      //    >< ZEBRA.codemirror
      //    mw.loader.load()
      //    REPOS.fire()
      // 2018-03-21 PerfektesChaos@de.wikipedia
      if ( ZEBRA.codemirror === false ) {
         ZEBRA.codemirror = null;
         mw.loader.load( [ "ext.CodeMirror" ] );
         REPOS.fire( JSOND.site,
                     JSOND.store + ".css",
                     false,
                     { action: "raw",
                       ctype:  "text/css",
                       bcache: 1,
                       maxage: 604800 } );
         REPOS.fire( JSOND.site,
                     JSOND.store + "/codemirrorJavascript.js",
                     false,
                     { action: "raw",
                       ctype:  "text/javascript",
                       bcache: 1,
                       maxage: 604800 } );
      }
   };   // ZEBRA.first()



   ZEBRA.focus = function ( at ) {
      // Highlight and focus particular line
      // Precondition:
      //    at  -- line number with error
      // Uses:
      //    >  Signature
      //    >  ZEBRA.editor
      // 2018-03-01 PerfektesChaos@de.wikipedia
      ZEBRA.editor.addLineClass( at,
                                 "background",
                                 Signature + "-line-error" );
      ZEBRA.editor.setCursor( at );
   };   // ZEBRA.focus()



   ZEBRA.furnish = function () {
      // Establish CodeMirror environment
      // Precondition:
      //    CodeMirror supposed to have been loaded
      // Uses:
      //    >  window.CodeMirror
      //    >< ZEBRA.codemirror
      //     < ZEBRA.editor
      //    mw.hook()
      //    (ZEBRA.fiat)
      // 2018-03-01 PerfektesChaos@de.wikipedia
      if ( ! ZEBRA.codemirror  &&
           typeof window.CodeMirror  !==  "undefined"
           &&     window.CodeMirror ) {
         ZEBRA.codemirror = window.CodeMirror;
         ZEBRA.editor     = false;
      }
      if ( ZEBRA.codemirror ) {
         mw.hook( Signature + ".cm-mode-js" ).add( ZEBRA.fiat );
      }
   };   // ZEBRA.furnish()



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



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

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