<commit_before>// Dependencies
var urltools = require('./../util/urltools');

// Expose defaults Extractors
module.exports = exports = [
   {
      route : 'default#description',
      operation : scrapDescription
   },
   {
      route : 'title',
      operation : scrapTitle
   },
   {
      route : 'default#images',
      operation : scrapImage
   },
   {
      route : 'default#videos',
      operation : scrapVideo
   }
];

// inline the text without /n and ' ' (space)
function inline(text){
   var explode = text.split('\n').join('').split(' ');
   var size = explode.length;
   for(var i=0; i < size ; i++){
      if(!explode[i]){
         explode.splice(i,1);
         i--;
         size--;
      }
   }
   return explode.join(' ').trim();
}

/**
 * Default description Scraper
 *
 */

// Scrap Everywhere
function scrapDescription($,url){
   var descriptions = [];

   // Open Graph protocol by Facebook <meta property="og:description" content="description text ..."/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:description' && content){
         descriptions.push(content);
      }
   });

   $('meta').each(function(){
      if($(this).attr('name') && $(this).attr('name').toLowerCase()=== 'description'){
         var description = inline($(this).attr('content')).trim();
         if(description) descriptions.push(description);
      }
   });
   $('div,p').each(function(){
      if( ($(this).attr('class') && $(this).attr('class').toLowerCase() === 'productdesc') || ($(this).attr('id') && $(this).attr('id').toLowerCase() === 'productdesc')){
         var description = inline($(this).text()).trim();
         if(description) descriptions.push(description);
      }
   });

   return descriptions;
};

/**
 * Default Image Scraper
 *
 */

// Regex url
var filterExtension = function(src){
   var extensions = {
      png : true,
      jpeg : true,
      jpg : true,
      webp : true
   };
   var accepted = false;
   var currentExtension = urltools.extension(src);
   if(extensions[currentExtension]){
      accepted = true;
   }
   return accepted;
}

function scrapImage($,url){
   var thumbs = [];
   var title = scrapTitle($,url);
   var addToThumbs = function(image){
      var src = $(image).attr('src');
      if(src && filterExtension(src) ){
         src = urltools.toURL(src,url);
         thumbs.push(src);
      }
   }

   $('img').each(function(){
      var alt = $(this).attr('alt');
      // Leave this test alone
      // the selector 'img[alt="title"]' will not work if the title is like LG 42PT35342" PLASMA TV. Escaping issues.
      if( alt === title){
         addToThumbs(this);
      }
   });

   // Open Graph protocol by Facebook <meta property="og:image" content="http://link/to/image.png"/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:image' && content){
         thumbs.push(urltools.toURL(content));
      }
   });

   if(thumbs.length < 1){
      $('img[id*="product"]').each(function(){
          addToThumbs(this);
      });

      $('img[class*="product"]').each(function(){
          addToThumbs(this);
      });
   }
   if(thumbs.length < 2){
      $('img').each(function(){
          addToThumbs(this);
      });
   }
   return thumbs;
}

/**
 * Default Title Scraper
 *
 */

function scrapTitle($,url){
   // IMPORTANT tags/attributes
   var tag =  $('title');
   var meta = $('meta[title]');
   var h = $('h1:first');
   var best = '';
   // Check
   tag = tag.text().trim() || '';
   meta = meta.attr('title') || '';
   h = h.text().trim() || '';
   // Regex
   var nameWebsite = urltools.webname(url);
   var regex = new RegExp(nameWebsite,'i');
   var matchTag = regex.test(tag);
   var matchMeta = regex.test(meta);
   var matchH = regex.test(h);

   // Try to get a text representing the title of the page that does not contain the name of the website.
   if( (!matchTag || !best) && !!tag) best = tag;
   if( (!matchMeta || !best) && !!meta) best = meta;
   if( (!matchH || !best) && !!h) best = h;
   return inline(best);
}

/**
 * Default Videos Scraper
 *
 */

function scrapVideo($,url){
   var thumbs = [];

   // Open Graph protocol by Facebook <meta property="og:video" content="link to the video"/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:video' && content){
         thumbs.push(content);
      }
   });

   $('video, embed').each(function(){
      var html = $(this).clone().wrap('

').parent().html();
      thumbs.push(html);
   });

   return thumbs;
}
<commit_msg>FIX: Refactoring + Close #13.<commit_after>// Dependencies
var urltools = require('./../util/urltools');

// Expose defaults Extractors
module.exports = exports = [
   {
      route : 'default#description',
      operation : scrapDescription
   },
   {
      route : 'title',
      operation : scrapTitle
   },
   {
      route : 'default#images',
      operation : scrapImage
   },
   {
      route : 'default#videos',
      operation : scrapVideo
   }
];

// inline the text without /n and ' ' (space)
function inline(text){
   var explode = text.split('\n').join('').split(' ');
   var size = explode.length;
   for(var i=0; i < size ; i++){
      if(!explode[i]){
         explode.splice(i,1);
         i--;
         size--;
      }
   }
   return explode.join(' ').trim();
}

/**
 * Default description Scraper
 *
 */

// Scrap Everywhere
function scrapDescription($,url){
   var descriptions = [];

   // Open Graph protocol by Facebook <meta property="og:description" content="(*)"/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:description' && content){
         descriptions.push(content);
      }
   });

   // Schema.org : <* itemprop="description">(*)</*>
   $('[itemprop="description"]').each(function(){
      var text = $(this).text();
      if(text){
         descriptions.push(content);
      }
   });

   // Meta tag description: <meta property="description" content="(*)" />
   $('meta').each(function(){
      if($(this).attr('name') && $(this).attr('name').toLowerCase()=== 'description'){
         var description = inline($(this).attr('content')).trim();
         if(description) descriptions.push(description);
      }
   });

   // Random text in div and p tags. Oriented product informations
   if(descriptions.length === 0){
      $('div,p').each(function(){
         if( ($(this).attr('class') && $(this).attr('class').toLowerCase() === 'productdesc') || ($(this).attr('id') && $(this).attr('id').toLowerCase() === 'productdesc')){
            var description = inline($(this).text()).trim();
            if(description) descriptions.push(description);
         }
      });
   }
   return descriptions;
};

/**
 * Default Image Scraper
 *
 */

var extensionsImage = {
   gif : false
};
// Permissive extension validator
// means even if the extension is not authorized explicitly the function considers the extension is valid (return true)
function validExtension(src){
   var extension = src.split('.').pop();
      if(extensionsImage[extension] === false){
         return false;
      }
      return true;
}

function scrapImage($,url){
   var thumbs = [];
   var title = scrapTitle($,url);
   var addToThumbs = function(image,beginning){
      var src = $(image).attr('src');
      if(src && validExtension(src) ){
         src = urltools.toURL(src,url);
         if(beginning){
            thumbs.unshift(src);
         }else{
            thumbs.push(src);
         }
      }
   }
   // Open Graph protocol by Facebook: <meta property="og:image" content="(*)"/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:image' && content){
         thumbs.push(urltools.toURL(content));
      }
   });

   // Schema.org:
   $('img[itemprop="image"]').each(function(){
      addToThumbs(this);
   });

   // Oriented product informations
   if(thumbs.length < 1){
      $('img[id*="product"]').each(function(){
          addToThumbs(this);
      });

      $('img[class*="product"]').each(function(){
          addToThumbs(this);
      });
   }

   // Grab all images
   if(thumbs.length < 10){
      $('img').each(function(){
         var alt = $(this).attr('alt');
         // Leave this test alone
         // the selector 'img[alt="title"]' will not work if the title is like LG 42PT35342" PLASMA TV. Escaping issues.
         // Image where the title of the page is equal to the content of the alt attribute of the image tag.
         if( alt === title){
            addToThumbs(this,true);
         }else{
            addToThumbs(this)
         }
      });
   }
   return thumbs;
}

/**
 * Default Title Scraper
 *
 */

function scrapTitle($,url){

   // Tags or attributes whom can contain a nice title for the page
   var titleTag =  $('title').text().trim();
   var metaTitleTag = $('meta[title]').attr('title');
   var h1Tag = $('h1:first').text().trim();
   var itempropNameTag = $('[itemprop="name"]').text().trim();
   var titles = [titleTag, metaTitleTag, h1Tag, itempropNameTag];

   // Regex of the web site name
   var nameWebsite = urltools.webname(url);
   var regex = new RegExp(nameWebsite,'i');

   // Sort to find the best title
   var titlesNotEmpty = titles.filter(function(value){
      if(value ){
         return true;
      }
      return false;
   });
   var titlesBest = titlesNotEmpty.filter(function(value){
      if(regex.test(value)){
         return false;
      }
      return true;
   });
   var bestTitle = (titlesBest && titlesBest[0]) || (titlesNotEmpty && titlesNotEmpty[0]) || '';
   return inline(bestTitle);
}

/**
 * Default Videos Scraper
 *
 */

function scrapVideo($,url){
   var thumbs = [];

   // Open Graph protocol by Facebook: <meta property="og:video" content="(*)"/>
   $('meta').each(function(){
      var property = $(this).attr('property');
      var content = $(this).attr('content');
      if(property === 'og:video' && content){
         thumbs.push(content);
      }
   });

   $('video, embed').each(function(){
      var html = $(this).clone().wrap('

').parent().html();
      thumbs.push(html);
   });

   return thumbs;
}
<|endoftext|>


<commit_before><commit_msg>Allow swapping meaning of _ and __ for lodash/underscore users.<commit_after><|endoftext|>


<commit_before>(function() {
    'use strict';

    var DIR         = './',

        fs          = require('fs'),
        path        = require('path'),
        jonny       = require('jonny'),
        mellow      = require('mellow'),
        ponse       = require('ponse'),
        WIN         = process.platform === 'win32',
        CWD         = process.cwd(),

        Fs          = {};

        [
            'get',
            'put',
            'patch',
            'delete'
        ].forEach(function(name) {
            Fs[name] = require(DIR + 'fs/' + name);
        });

    module.exports = function(options) {
        return middle.bind(null, options || {});
    };

    function middle(options, req, res, next) {
        var name, is,
            isFile  = req.url === '/restafary.js',
            prefix  = options.prefix || '/fs',
            root    = options.root || '/',

            params  = {
                request : req,
                response: res,
                root    : root
            };

        name    = ponse.getPathName(req);
        is      = !name.indexOf(prefix);

        if (isFile)
            sendFile(req, res);
        else if (!is) {
            next();
        } else {
            name = name.replace(prefix, '') || '/';

            params.name = name;

            onFS(params, function(error, options, data) {
                params.gzip = !error;

                if (options.name)
                    params.name = options.name;

                if (options.gzip !== undefined)
                    params.gzip = options.gzip;

                if (options.query)
                    params.query = options.query;

                if (error) {
                    ponse.sendError(error, params);
                } else {
                    if (!data)
                        data = getMsg(name, req);

                    ponse.send(data, params);
                }
            });
        }
    }

    function sendFile(req, res) {
        var name = __dirname + '/client.js';

        ponse.sendFile({
            name: name,
            gzip: true,
            request: req,
            response: res
        });
    }

    function getMsg(name, req) {
        var msg,
            query   = ponse.getQuery(req),
            method  = req.method.toLowerCase();

        name        = path.basename(name);

        if (method !== 'put') {
            msg     = method;
        } else {
            if (query === 'dir')
                msg = 'make dir';
            else
                msg = 'save';
        }

        msg     = format(msg, name);

        return msg;
    }

    function checkPath(name, root) {
        var ok,
            drive   = name.split('/')[1],
            isRoot  = root === '/',
            isDrive = /^[a-z]$/i.test(drive);

        ok = !WIN || !isRoot || isDrive;

        return ok;
    }

    function onFS(params, callback) {
        var root,
            rootWin,
            pathOS,
            pathWeb,
            pathError       = 'Could not write file/create directory in root on windows!',
            p               = params,
            name            = p.name,
            query           = ponse.getQuery(p.request),
            optionsDefaults  = {
                gzip: false,
                name: '.txt'
            };

        if (typeof params.root === 'function')
            root = params.root();
        else
            root = params.root;

        root = handleDotFolder(root);
        rootWin = root.replace('/', '\\');
        pathOS  = mellow.pathToWin(name, root);
        pathWeb = path.join(root, name);

        if (WIN && pathWeb.indexOf(rootWin) || !WIN && pathWeb.indexOf(root))
            return callback(Error('Path ' + pathWeb + ' beyond root ' + root + '!'), optionsDefaults);

        switch (p.request.method) {
        case 'PUT':
            if (!checkPath(name, root))
                callback(pathError, optionsDefaults);
            else
                Fs.put(query, pathOS, p.request, function(error) {
                    callback(error, optionsDefaults);
                });
            break;

         case 'PATCH':
            Fs.patch(path, p.request, function(error) {
                callback(error, optionsDefaults);
            });
            break;

        case 'GET':
            Fs.get(query, pathOS, function(error, data) {
                onGet({
                    error: error,
                    name: p.name,
                    path: pathOS,
                    query: query,
                    request: p.request,
                    response: p.response,
                    data: data
                }, callback);
            });
            break;

        case 'DELETE':
            Fs.delete(query, pathOS, p.request, function(error) {
                callback(error, optionsDefaults);
            });
            break;
        }
    }

    function onGet(p, callback) {
        var str,
            options = {},
            isFile  = p.error && p.error.code === 'ENOTDIR',
            isStr   = typeof p.data === 'string',
            params  = {
                gzip: true,
                name: p.path,
                request: p.request,
                response: p.response,
            };

        if (isFile) {
            fs.realpath(p.path, function(error, path) {
                if (!error)
                    params.name = path;

                params.gzip = false;
                ponse.sendFile(params);
            });
        } else {
            if (!p.error)
                if (/^(size|time|hash|beautify|minify)$/.test(p.query)) {
                    str = String(p.data);
                } else {
                    p.data.path   = addSlashToEnd(p.name);

                    if (p.name === '/')
                        p.name += 'fs';

                    options     = {
                        name    : p.name + 'fs.json',
                        query   : p.query
                    };

                    if (isStr)
                        str = p.data;
                    else
                        str = jonny.stringify(p.data, null, 4);
                }

            callback(p.error, options, str);
        }
    }

    function format(msg, name) {
        var status = 'ok';

        if (name)
            name = '("' + name + '")';

        msg = msg + ': ' + status + name;

        return msg;
    }

    function addSlashToEnd(path) {
        var length, isSlash;

        if (path) {
            length  = path.length - 1;
            isSlash = path[length] === '/';

            if (!isSlash)
                path += '/';
        }

        return path;
    }

    function handleDotFolder(root) {
        return root.replace(/^\.(\/|\\|$)/, CWD);
    }

})();
<commit_msg>chore(restafary) rm iife<commit_after>'use strict';

var DIR         = './',

    fs          = require('fs'),
    path        = require('path'),
    jonny       = require('jonny'),
    mellow      = require('mellow'),
    ponse       = require('ponse'),
    WIN         = process.platform === 'win32',
    CWD         = process.cwd(),

    Fs          = {};

    [
        'get',
        'put',
        'patch',
        'delete'
    ].forEach(function(name) {
        Fs[name] = require(DIR + 'fs/' + name);
    });

module.exports = function(options) {
    return middle.bind(null, options || {});
};

function middle(options, req, res, next) {
    var name, is,
        isFile  = req.url === '/restafary.js',
        prefix  = options.prefix || '/fs',
        root    = options.root || '/',

        params  = {
            request : req,
            response: res,
            root    : root
        };

    name    = ponse.getPathName(req);
    is      = !name.indexOf(prefix);

    if (isFile)
        sendFile(req, res);
    else if (!is) {
        next();
    } else {
        name = name.replace(prefix, '') || '/';

        params.name = name;

        onFS(params, function(error, options, data) {
            params.gzip = !error;

            if (options.name)
                params.name = options.name;

            if (options.gzip !== undefined)
                params.gzip = options.gzip;

            if (options.query)
                params.query = options.query;

            if (error) {
                ponse.sendError(error, params);
            } else {
                if (!data)
                    data = getMsg(name, req);

                ponse.send(data, params);
            }
        });
    }
}

function sendFile(req, res) {
    var name = __dirname + '/client.js';

    ponse.sendFile({
        name: name,
        gzip: true,
        request: req,
        response: res
    });
}

function getMsg(name, req) {
    var msg,
        query   = ponse.getQuery(req),
        method  = req.method.toLowerCase();

    name        = path.basename(name);

    if (method !== 'put') {
        msg     = method;
    } else {
        if (query === 'dir')
            msg = 'make dir';
        else
            msg = 'save';
    }

    msg     = format(msg, name);

    return msg;
}

function checkPath(name, root) {
    var ok,
        drive   = name.split('/')[1],
        isRoot  = root === '/',
        isDrive = /^[a-z]$/i.test(drive);

    ok = !WIN || !isRoot || isDrive;

    return ok;
}

function onFS(params, callback) {
    var root,
        rootWin,
        pathOS,
        pathWeb,
        pathError       = 'Could not write file/create directory in root on windows!',
        p               = params,
        name            = p.name,
        query           = ponse.getQuery(p.request),
        optionsDefaults  = {
            gzip: false,
            name: '.txt'
        };

    if (typeof params.root === 'function')
        root = params.root();
    else
        root = params.root;

    root = handleDotFolder(root);
    rootWin = root.replace('/', '\\');
    pathOS  = mellow.pathToWin(name, root);
    pathWeb = path.join(root, name);

    if (WIN && pathWeb.indexOf(rootWin) || !WIN && pathWeb.indexOf(root))
        return callback(Error('Path ' + pathWeb + ' beyond root ' + root + '!'), optionsDefaults);

    switch (p.request.method) {
    case 'PUT':
        if (!checkPath(name, root))
            callback(pathError, optionsDefaults);
        else
            Fs.put(query, pathOS, p.request, function(error) {
                callback(error, optionsDefaults);
            });
        break;

     case 'PATCH':
        Fs.patch(path, p.request, function(error) {
            callback(error, optionsDefaults);
        });
        break;

    case 'GET':
        Fs.get(query, pathOS, function(error, data) {
            onGet({
                error: error,
                name: p.name,
                path: pathOS,
                query: query,
                request: p.request,
                response: p.response,
                data: data
            }, callback);
        });
        break;

    case 'DELETE':
        Fs.delete(query, pathOS, p.request, function(error) {
            callback(error, optionsDefaults);
        });
        break;
    }
}

function onGet(p, callback) {
    var str,
        options = {},
        isFile  = p.error && p.error.code === 'ENOTDIR',
        isStr   = typeof p.data === 'string',
        params  = {
            gzip: true,
            name: p.path,
            request: p.request,
            response: p.response,
        };

    if (isFile) {
        fs.realpath(p.path, function(error, path) {
            if (!error)
                params.name = path;

            params.gzip = false;
            ponse.sendFile(params);
        });
    } else {
        if (!p.error)
            if (/^(size|time|hash|beautify|minify)$/.test(p.query)) {
                str = String(p.data);
            } else {
                p.data.path   = addSlashToEnd(p.name);

                if (p.name === '/')
                    p.name += 'fs';

                options     = {
                    name    : p.name + 'fs.json',
                    query   : p.query
                };

                if (isStr)
                    str = p.data;
                else
                    str = jonny.stringify(p.data, null, 4);
            }

        callback(p.error, options, str);
    }
}

function format(msg, name) {
    var status = 'ok';

    if (name)
        name = '("' + name + '")';

    msg = msg + ': ' + status + name;

    return msg;
}

function addSlashToEnd(path) {
    var length, isSlash;

    if (path) {
        length  = path.length - 1;
        isSlash = path[length] === '/';

        if (!isSlash)
            path += '/';
    }

    return path;
}

function handleDotFolder(root) {
    return root.replace(/^\.(\/|\\|$)/, CWD);
}

<|endoftext|>


<commit_before>var utils = require('../utils');

/**
 * Creates a Connect/Express compatible middleware bound to a Server
 *  @class Jayson JSON-RPC Middleware
 *  @param {Server} server Server instance
 *  @param {Object} [options] Options for this function
 *  @return {Function}
 *  @api public
 */
var Middleware = function(server, options) {
  return function(req, res, next) {
    var options = utils.merge(server.options, options || {});

    //  405 method not allowed if not POST
    if(!utils.isMethod(req, 'POST')) return error(405, { 'allow': 'POST' });

    // 415 unsupported media type if Content-Type is not correct
    if(!utils.isContentType(req, 'application/json')) return error(415);

    // body does not appear to be parsed, 500 server error
    if(!req.body || typeof(req.body) !== 'object') return next(new Error('Request body must be parsed'));

    server.call(req.body, function(error, success) {
      var response = error || success;

      utils.JSON.stringify(response, options, function(err, body) {
        if(err) return next(err);

        // empty response?
        if(body) {
          var headers = {
            "Content-Length": Buffer.byteLength(body, options.encoding),
            "Content-Type": "application/json"
          };
          res.writeHead(200, headers);
          res.write(body);
          res.end();
        } else {
          res.writeHead(204);
          res.end();
        }

      });
    });

    // ends the request with an error code
    function error(code, headers) {
      res.writeHead(code, headers || {});
      res.end();
    }
  };
};

module.exports = Middleware;
<commit_msg>Fix bug in Server.Middleware options setting<commit_after>var utils = require('../utils');

/**
 * Creates a Connect/Express compatible middleware bound to a Server
 *  @class Jayson JSON-RPC Middleware
 *  @param {Server} server Server instance
 *  @param {Object} [outerOptions] Specific options for the middleware
 *  @return {Function}
 *  @api public
 */
var Middleware = function(server, outerOptions) {
  return function(req, res, next) {
    var options = utils.merge(server.options, outerOptions || {});

    //  405 method not allowed if not POST
    if(!utils.isMethod(req, 'POST')) return error(405, { 'allow': 'POST' });

    // 415 unsupported media type if Content-Type is not correct
    if(!utils.isContentType(req, 'application/json')) return error(415);

    // body does not appear to be parsed, 500 server error
    if(!req.body || typeof(req.body) !== 'object') return next(new Error('Request body must be parsed'));

    server.call(req.body, function(error, success) {
      var response = error || success;

      utils.JSON.stringify(response, options, function(err, body) {
        if(err) return next(err);

        // empty response?
        if(body) {
          var headers = {
            "Content-Length": Buffer.byteLength(body, options.encoding),
            "Content-Type": "application/json"
          };
          res.writeHead(200, headers);
          res.write(body);
          res.end();
        } else {
          res.writeHead(204);
          res.end();
        }

      });
    });

    // ends the request with an error code
    function error(code, headers) {
      res.writeHead(code, headers || {});
      res.end();
    }
  };
};

module.exports = Middleware;
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var Util = require('../util');

    module.exports  = function tryRequire(name, options) {
        var module,
            o       = options || {},

            error   = Util.exec.try(function() {
                module = require(name);
            });

        if (error && o.log)
            Util.log(error.message);

        return module;
    };
})();
<commit_msg>feature(tryRequire) options: add callback<commit_after>(function() {
    'use strict';

    var Util = require('../util');

    module.exports  = function tryRequire(name, options) {
        var module,
            o       = options || {},

            error   = Util.exec.try(function() {
                module = require(name);
            });

        if (error)
            if (o.log)
                Util.log(error.message);
            else if (o.callback)
                module = exec.bind(null, error);

        return module;
    };

    function exec(error, callback) {
        callback(error);
    }
})();
<|endoftext|>


<commit_before>// PORTAL DETAILS MAIN ///////////////////////////////////////////////
// main code block that renders the portal details in the sidebar and
// methods that highlight the portal in the map view.

window.renderPortalDetails = function(guid) {
  selectPortal(window.portals[guid] ? guid : null);

  if (guid && !portalDetail.isFresh(guid)) {
    portalDetail.request(guid);
  }

  // TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
  // window.portals....

  if(!window.portals[guid]) {
    urlPortal = guid;
    $('#portaldetails').html('');
    if(isSmartphone()) {
      $('.fullimg').remove();
      $('#mobileinfo').html('
tap here for info screen
');
    }
    return;
  }

  var portal = window.portals[guid];
  var data = portal.options.data;
  var details = portalDetail.get(guid);

  // details and data can get out of sync. if we have details, construct a matching 'data'
  if (details) {
    data = getPortalSummaryData(details);
  }

  var modDetails = details ? '
'+getModDetails(details)+'
' : '';
  var miscDetails = details ? getPortalMiscDetails(guid,details) : '';
  var resoDetails = details ? getResonatorDetails(details) : '';

//TODO? other status details...
  var statusDetails = details ? '' : '
Loading details...
';

  var img = fixPortalImageUrl(details ? details.image : data.image);
  var title = data.title;

  var lat = data.latE6/1E6;
  var lng = data.lngE6/1E6;

  var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title;
  imgTitle += '\n\nClick to show full image.';
  var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined;

  var portalDetailedDescription = '';

  if(portalDetailObj) {
    portalDetailedDescription = '
';

    // TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them
    // (at this time the data isn't returned from the server - although a count of images IS returned!)

    if(portalDetailObj.submitter.name.length > 0) {
      if(portalDetailObj.submitter.team) {
        submitterSpan = '';
      } else {
        submitterSpan = '';
      }
      portalDetailedDescription += '
Photo by:	' + submitterSpan
                                + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
';
    }
    if(portalDetailObj.submitter.link.length > 0) {
      portalDetailedDescription += '
Photo from:	' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
';
    }

    if(portalDetailObj.description) {
      portalDetailedDescription += '
Description:	' + escapeHtmlSpecialChars(portalDetailObj.description) + '
';
    }
//    if(d.descriptiveText.map.ADDRESS) {
//      portalDetailedDescription += '
Address:	' + escapeHtmlSpecialChars(d.descriptiveText.map.ADDRESS) + '
';
//    }

    portalDetailedDescription += '
';
  }

  // portal level. start with basic data - then extend with fractional info in tooltip if available
  var levelInt = (teamStringToId(data.team) == TEAM_NONE) ? 0 : data.level;
  var levelDetails = levelInt;
  if (details) {
    levelDetails = getPortalLevel(details);
    if(levelDetails != 8) {
      if(levelDetails==Math.ceil(levelDetails))
        levelDetails += "\n8";
      else
        levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
      levelDetails += " resonator level(s) needed for next portal level";
    } else {
      levelDetails += "\nfully upgraded";
    }
  }
  levelDetails = "Level " + levelDetails;

  var linkDetails = [];

  var posOnClick = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(title)+'\')';
  var permalinkUrl = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;

  if (typeof android !== 'undefined' && android && android.intentPosLink) {
    // android devices. one share link option - and the android app provides an interface to share the URL,
    // share as a geo: intent (navigation via google maps), etc

    var shareLink = $('
').html( $('').attr({onclick:posOnClick}).text('Share portal') ).html();
    linkDetails.push(''+shareLink+'');

  } else {
    // non-android - a permalink for the portal
    var permaHtml = $('
').html( $('').attr({href:permalinkUrl, title:'Create a URL link to this portal'}).text('Portal link') ).html();
    linkDetails.push ( ''+permaHtml+'' );

    // and a map link popup dialog
    var mapHtml = $('
').html( $('').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html();
    linkDetails.push(''+mapHtml+'');

  }

  $('#portaldetails')
    .html('') //to ensure it's clear
    .attr('class', TEAM_TO_CSS[teamStringToId(data.team)])
    .append(
      $('
').attr({class:'title'}).text(data.title),

      $('').attr({class:'close', onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',title:'Close'}).text('X'),

      // help cursor via ".imgpreview img"
      $('
')
      .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"})
      .append(
        $('').attr({id:'level', title: levelDetails}).text(levelInt),
        $('
').attr({class:'portalDetails'}).html(portalDetailedDescription),
        $('').attr({class:'hide', src:img})
      ),

      modDetails,
      miscDetails,
      resoDetails,
      statusDetails,
      '
' + linkDetails.join('') + '
'
    );

  // only run the hooks when we have a portalDetails object - most plugins rely on the extended data
  // TODO? another hook to call always, for any plugins that can work with less data?
  if (details) {
    runHooks('portalDetailsUpdated', {guid: guid, portal: portal, portalDetails: details, portalData: data});
  }
}

window.getPortalMiscDetails = function(guid,d) {

  var randDetails;

  if (d) {

    // collect some random data that’s not worth to put in an own method
    var linkInfo = getPortalLinks(guid);
    var links = {incoming: linkInfo.in.length, outgoing: linkInfo.out.length};

    function linkExpl(t) { return '<tt title="'+links.outgoing+' links out (8 max)\n'+links.incoming+' links in\n('+(links.outgoing+links.incoming)+' total)">'+t+'</tt>'; }
    var linksText = [linkExpl('links'), linkExpl(links.outgoing+' out / '+links.incoming+' in')];

    var player = d.owner
      ? '' + d.owner + ''
      : null;
    var playerText = player ? ['owner', player] : null;

    var time = d.capturedTime
      ? ''
        +  unixTimeToString(d.capturedTime) + ''
      : null;
    var sinceText  = time ? ['since', time] : null;

    var fieldCount = getPortalFieldsCount(guid);

    var linkedFields = ['fields', fieldCount];

    var linkCount = getPortalLinksCount(guid);

    // collect and html-ify random data
    var randDetailsData = [];
    if (playerText && sinceText) {
      randDetailsData.push (playerText, sinceText);
    }

    randDetailsData.push (
      getRangeText(d), getEnergyText(d),
      linksText, ['-','-'],
      linkedFields, getAttackApGainText(d,fieldCount,linkCount),
      getHackDetailsText(d), getMitigationText(d,linkCount)
    );

    // artifact details

    // 2014-02-06: stock site changed from supporting 'jarvis shards' to 'amar artifacts'(?) - so let's see what we can do to be generic...
    $.each(artifact.getArtifactTypes(),function(index,type) {
      var artdata = artifact.getPortalData (guid, type);
      if (artdata) {
        var details = artifact.getArtifactDescriptions(type);
        if (details) {
          // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns
          var target = ['',''], shards = [details.fragmentName,'(none)'];
          if (artdata.target) {
            target = ['target', ''+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+''];
          }
          if (artdata.fragments) {
            shards = [details.fragmentName, '#'+artdata.fragments.join(', #')];
          }

          randDetailsData.push (target, shards);
        } else {
          console.warn('Unknown artifact type '+type+': no names, so cannot display');
        }
      }
    });

    randDetails = '
' + genFourColumnTable(randDetailsData) + '
';

  }

  return randDetails;
}

// draws link-range and hack-range circles around the portal with the
// given details. Clear them if parameter 'd' is null.
window.setPortalIndicators = function(p) {

  if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
  portalRangeIndicator = null;
  if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
  portalAccessIndicator = null;

  // if we have a portal...

  if(p) {
    var coord = p.getLatLng();

    // range is only known for sure if we have portal details
    // TODO? render a min range guess until details are loaded..?

    var d = portalDetail.get(p.options.guid);
    if (d) {
      var range = getPortalRange(d);
      portalRangeIndicator = (range.range > 0
          ? L.geodesicCircle(coord, range.range, {
              fill: false,
              color: RANGE_INDICATOR_COLOR,
              weight: 3,
              dashArray: range.isLinkable ? undefined : "10,10",
              clickable: false })
          : L.circle(coord, range.range, { fill: false, stroke: false, clickable: false })
        ).addTo(map);
    }

    portalAccessIndicator = L.circle(coord, HACK_RANGE,
      { fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
    ).addTo(map);
  }

}

// highlights portal with given GUID. Automatically clears highlights
// on old selection. Returns false if the selected portal changed.
// Returns true if it's still the same portal that just needs an
// update.
window.selectPortal = function(guid) {
  var update = selectedPortal === guid;
  var oldPortalGuid = selectedPortal;
  selectedPortal = guid;

  var oldPortal = portals[oldPortalGuid];
  var newPortal = portals[guid];

  // Restore style of unselected portal
  if(!update && oldPortal) setMarkerStyle(oldPortal,false);

  // Change style of selected portal
  if(newPortal) {
    setMarkerStyle(newPortal, true);

    if (map.hasLayer(newPortal)) {
      newPortal.bringToFront();
    }
  }

  setPortalIndicators(newPortal);

  runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
  return update;
}
<commit_msg>quick fix for capture date removal - could do with re-formatting the sidebar data to use the space better<commit_after>// PORTAL DETAILS MAIN ///////////////////////////////////////////////
// main code block that renders the portal details in the sidebar and
// methods that highlight the portal in the map view.

window.renderPortalDetails = function(guid) {
  selectPortal(window.portals[guid] ? guid : null);

  if (guid && !portalDetail.isFresh(guid)) {
    portalDetail.request(guid);
  }

  // TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
  // window.portals....

  if(!window.portals[guid]) {
    urlPortal = guid;
    $('#portaldetails').html('');
    if(isSmartphone()) {
      $('.fullimg').remove();
      $('#mobileinfo').html('
tap here for info screen
');
    }
    return;
  }

  var portal = window.portals[guid];
  var data = portal.options.data;
  var details = portalDetail.get(guid);

  // details and data can get out of sync. if we have details, construct a matching 'data'
  if (details) {
    data = getPortalSummaryData(details);
  }

  var modDetails = details ? '
'+getModDetails(details)+'
' : '';
  var miscDetails = details ? getPortalMiscDetails(guid,details) : '';
  var resoDetails = details ? getResonatorDetails(details) : '';

//TODO? other status details...
  var statusDetails = details ? '' : '
Loading details...
';

  var img = fixPortalImageUrl(details ? details.image : data.image);
  var title = data.title;

  var lat = data.latE6/1E6;
  var lng = data.lngE6/1E6;

  var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title;
  imgTitle += '\n\nClick to show full image.';
  var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined;

  var portalDetailedDescription = '';

  if(portalDetailObj) {
    portalDetailedDescription = '
';

    // TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them
    // (at this time the data isn't returned from the server - although a count of images IS returned!)

    if(portalDetailObj.submitter.name.length > 0) {
      if(portalDetailObj.submitter.team) {
        submitterSpan = '';
      } else {
        submitterSpan = '';
      }
      portalDetailedDescription += '
Photo by:	' + submitterSpan
                                + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
';
    }
    if(portalDetailObj.submitter.link.length > 0) {
      portalDetailedDescription += '
Photo from:	' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
';
    }

    if(portalDetailObj.description) {
      portalDetailedDescription += '
Description:	' + escapeHtmlSpecialChars(portalDetailObj.description) + '
';
    }
//    if(d.descriptiveText.map.ADDRESS) {
//      portalDetailedDescription += '
Address:	' + escapeHtmlSpecialChars(d.descriptiveText.map.ADDRESS) + '
';
//    }

    portalDetailedDescription += '
';
  }

  // portal level. start with basic data - then extend with fractional info in tooltip if available
  var levelInt = (teamStringToId(data.team) == TEAM_NONE) ? 0 : data.level;
  var levelDetails = levelInt;
  if (details) {
    levelDetails = getPortalLevel(details);
    if(levelDetails != 8) {
      if(levelDetails==Math.ceil(levelDetails))
        levelDetails += "\n8";
      else
        levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
      levelDetails += " resonator level(s) needed for next portal level";
    } else {
      levelDetails += "\nfully upgraded";
    }
  }
  levelDetails = "Level " + levelDetails;

  var linkDetails = [];

  var posOnClick = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(title)+'\')';
  var permalinkUrl = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;

  if (typeof android !== 'undefined' && android && android.intentPosLink) {
    // android devices. one share link option - and the android app provides an interface to share the URL,
    // share as a geo: intent (navigation via google maps), etc

    var shareLink = $('
').html( $('').attr({onclick:posOnClick}).text('Share portal') ).html();
    linkDetails.push(''+shareLink+'');

  } else {
    // non-android - a permalink for the portal
    var permaHtml = $('
').html( $('').attr({href:permalinkUrl, title:'Create a URL link to this portal'}).text('Portal link') ).html();
    linkDetails.push ( ''+permaHtml+'' );

    // and a map link popup dialog
    var mapHtml = $('
').html( $('').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html();
    linkDetails.push(''+mapHtml+'');

  }

  $('#portaldetails')
    .html('') //to ensure it's clear
    .attr('class', TEAM_TO_CSS[teamStringToId(data.team)])
    .append(
      $('
').attr({class:'title'}).text(data.title),

      $('').attr({class:'close', onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',title:'Close'}).text('X'),

      // help cursor via ".imgpreview img"
      $('
')
      .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"})
      .append(
        $('').attr({id:'level', title: levelDetails}).text(levelInt),
        $('
').attr({class:'portalDetails'}).html(portalDetailedDescription),
        $('').attr({class:'hide', src:img})
      ),

      modDetails,
      miscDetails,
      resoDetails,
      statusDetails,
      '
' + linkDetails.join('') + '
'
    );

  // only run the hooks when we have a portalDetails object - most plugins rely on the extended data
  // TODO? another hook to call always, for any plugins that can work with less data?
  if (details) {
    runHooks('portalDetailsUpdated', {guid: guid, portal: portal, portalDetails: details, portalData: data});
  }
}

window.getPortalMiscDetails = function(guid,d) {

  var randDetails;

  if (d) {

    // collect some random data that’s not worth to put in an own method
    var linkInfo = getPortalLinks(guid);
    var links = {incoming: linkInfo.in.length, outgoing: linkInfo.out.length};

    function linkExpl(t) { return '<tt title="'+links.outgoing+' links out (8 max)\n'+links.incoming+' links in\n('+(links.outgoing+links.incoming)+' total)">'+t+'</tt>'; }
    var linksText = [linkExpl('links'), linkExpl(links.outgoing+' out / '+links.incoming+' in')];

    var player = d.owner
      ? '' + d.owner + ''
      : null;
    var playerText = player ? ['owner', player] : null;

    var time = d.capturedTime
      ? ''
        +  unixTimeToString(d.capturedTime) + ''
      : null;
    var sinceText  = time ? ['since', time] : null;

    var fieldCount = getPortalFieldsCount(guid);

    var linkedFields = ['fields', fieldCount];

    var linkCount = getPortalLinksCount(guid);

    // collect and html-ify random data
    var randDetailsData = [];
    if (playerText || sinceText) {
      randDetailsData.push (playerText || ['-','-'], sinceText || ['-','-']);
    }

    randDetailsData.push (
      getRangeText(d), getEnergyText(d),
      linksText, ['-','-'],
      linkedFields, getAttackApGainText(d,fieldCount,linkCount),
      getHackDetailsText(d), getMitigationText(d,linkCount)
    );

    // artifact details

    // 2014-02-06: stock site changed from supporting 'jarvis shards' to 'amar artifacts'(?) - so let's see what we can do to be generic...
    $.each(artifact.getArtifactTypes(),function(index,type) {
      var artdata = artifact.getPortalData (guid, type);
      if (artdata) {
        var details = artifact.getArtifactDescriptions(type);
        if (details) {
          // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns
          var target = ['',''], shards = [details.fragmentName,'(none)'];
          if (artdata.target) {
            target = ['target', ''+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+''];
          }
          if (artdata.fragments) {
            shards = [details.fragmentName, '#'+artdata.fragments.join(', #')];
          }

          randDetailsData.push (target, shards);
        } else {
          console.warn('Unknown artifact type '+type+': no names, so cannot display');
        }
      }
    });

    randDetails = '
' + genFourColumnTable(randDetailsData) + '
';

  }

  return randDetails;
}

// draws link-range and hack-range circles around the portal with the
// given details. Clear them if parameter 'd' is null.
window.setPortalIndicators = function(p) {

  if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
  portalRangeIndicator = null;
  if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
  portalAccessIndicator = null;

  // if we have a portal...

  if(p) {
    var coord = p.getLatLng();

    // range is only known for sure if we have portal details
    // TODO? render a min range guess until details are loaded..?

    var d = portalDetail.get(p.options.guid);
    if (d) {
      var range = getPortalRange(d);
      portalRangeIndicator = (range.range > 0
          ? L.geodesicCircle(coord, range.range, {
              fill: false,
              color: RANGE_INDICATOR_COLOR,
              weight: 3,
              dashArray: range.isLinkable ? undefined : "10,10",
              clickable: false })
          : L.circle(coord, range.range, { fill: false, stroke: false, clickable: false })
        ).addTo(map);
    }

    portalAccessIndicator = L.circle(coord, HACK_RANGE,
      { fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
    ).addTo(map);
  }

}

// highlights portal with given GUID. Automatically clears highlights
// on old selection. Returns false if the selected portal changed.
// Returns true if it's still the same portal that just needs an
// update.
window.selectPortal = function(guid) {
  var update = selectedPortal === guid;
  var oldPortalGuid = selectedPortal;
  selectedPortal = guid;

  var oldPortal = portals[oldPortalGuid];
  var newPortal = portals[guid];

  // Restore style of unselected portal
  if(!update && oldPortal) setMarkerStyle(oldPortal,false);

  // Change style of selected portal
  if(newPortal) {
    setMarkerStyle(newPortal, true);

    if (map.hasLayer(newPortal)) {
      newPortal.bringToFront();
    }
  }

  setPortalIndicators(newPortal);

  runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
  return update;
}
<|endoftext|>


<commit_before>// PORTAL DETAILS MAIN ///////////////////////////////////////////////
// main code block that renders the portal details in the sidebar and
// methods that highlight the portal in the map view.

window.renderPortalDetails = function(guid) {
  selectPortal(window.portals[guid] ? guid : null);

  if (!portalDetail.isFresh(guid)) {
    portalDetail.request(guid);
  }

  // TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
  // window.portals....

  if(!window.portals[guid]) {
    urlPortal = guid;
    $('#portaldetails').html('');
    if(isSmartphone()) {
      $('.fullimg').remove();
      $('#mobileinfo').html('
tap here for info screen
');
    }
    return;
  }

  var portal = window.portals[guid];
  var data = portal.options.data;
  var details = portalDetail.get(guid);

  var modDetails = details ? '
'+getModDetails(details)+'
' : '';
  var miscDetails = details ? getPortalMiscDetails(guid,details) : '';
  var resoDetails = details ? getResonatorDetails(details) : '';

//TODO? other status details...
  var statusDetails = details ? '' : '
Loading details...
';

  var img = fixPortalImageUrl(details ? details.imageByUrl && details.imageByUrl.imageUrl : data.image);
  var title = details ? details.portalV2.descriptiveText.TITLE : data.title;

  var lat = data.latE6/1E6;
  var lng = data.lngE6/1E6;

  var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title;
  imgTitle += '\n\nClick to show full image.';
  var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined;

  var portalDetailedDescription = '';

  if(portalDetailObj) {
    portalDetailedDescription = '
';

    // TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them
    // (at this time the data isn't returned from the server - although a count of images IS returned!)

    if(portalDetailObj.submitter.name.length > 0) {
      if(portalDetailObj.submitter.team) {
        submitterSpan = '';
      } else {
        submitterSpan = '';
      }
      portalDetailedDescription += '
Photo by:	' + submitterSpan
                                + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
';
    }
    if(portalDetailObj.submitter.link.length > 0) {
      portalDetailedDescription += '
Photo from:	' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
';
    }

    if(portalDetailObj.description) {
      portalDetailedDescription += '
Description:	' + escapeHtmlSpecialChars(portalDetailObj.description) + '
';
    }
//    if(d.portalV2.descriptiveText.ADDRESS) {
//      portalDetailedDescription += '
Address:	' + escapeHtmlSpecialChars(d.portalV2.descriptiveText.ADDRESS) + '
';
//    }

    portalDetailedDescription += '
';
  }

  // portal level. start with basic data - then extend with fractional info in tooltip if available
  var levelInt = data ? data.level : getPortalLevel(details);
  var levelDetails = data.level;
  if (details) {
    levelDetails = getPortalLevel(details);
    if(levelDetails != 8) {
      if(levelDetails==Math.ceil(levelDetails))
        levelDetails += "\n8";
      else
        levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
      levelDetails += " resonator level(s) needed for next portal level";
    } else {
      levelDetails += "\nfully upgraded";
    }
  }
  levelDetails = "Level " + levelDetails;

  var linkDetails = [];

  var posOnClick = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(title)+'\')';
  var permalinkUrl = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;

  if (typeof android !== 'undefined' && android && android.intentPosLink) {
    // android devices. one share link option - and the android app provides an interface to share the URL,
    // share as a geo: intent (navigation via google maps), etc

    var shareLink = $('
').html( $('').attr({onclick:posOnClick}).text('Share portal') ).html();
    linkDetails.push(''+shareLink+'');

  } else {
    // non-android - a permalink for the portal
    var permaHtml = $('
').html( $('').attr({href:permalinkUrl, target:'_blank', title:'Create a URL link to this portal'}).text('Portal link') ).html();
    linkDetails.push ( ''+permaHtml+'' );

    // and a map link popup dialog
    var mapHtml = $('
').html( $('').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html();
    linkDetails.push(''+mapHtml+'');

  }

  $('#portaldetails')
    .html('') //to ensure it's clear
    .attr('class', TEAM_TO_CSS[portal.options.team])
    .append(
      $('
').attr({class:'title'}).text(data.title),

      $('').attr({class:'close', onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',title:'Close'}).text('X'),

      // help cursor via ".imgpreview img"
      $('
')
      .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"})
      .append(
        $('').attr({id:'level', title: levelDetails}).text(levelInt),
        $('
').attr({class:'portalDetails'}).html(portalDetailedDescription),
        $('').attr({class:'hide', src:img})
      ),

      modDetails,
      miscDetails,
      resoDetails,
      statusDetails,
      '
' + linkDetails.join('') + '
'
    );

  // only run the hooks when we have a portalDetails object - most plugins rely on the extended data
  // TODO? another hook to call always, for any plugins that can work with less data?
  if (details) {
    runHooks('portalDetailsUpdated', {guid: guid, portal: portal, portalDetails: details, portalData: data});
  }
}

window.getPortalMiscDetails = function(guid,d) {

  var randDetails;

  if (d) {

    // collect some random data that’s not worth to put in an own method
    var links = {incoming: 0, outgoing: 0};
    $.each(d.portalV2.linkedEdges||[], function(ind, link) {
      links[link.isOrigin ? 'outgoing' : 'incoming']++;
    });

    function linkExpl(t) { return '<tt title="↳ incoming links\n↴ outgoing links\n• is the portal">'+t+'</tt>'; }
    var linksText = [linkExpl('links'), linkExpl(' ↳ ' + links.incoming+'  •  '+links.outgoing+' ↴')];

    var player = d.captured && d.captured.capturingPlayerId
      ? '' + d.captured.capturingPlayerId + ''
      : null;
    var playerText = player ? ['owner', player] : null;

    var time = d.captured
      ? ''
        +  unixTimeToString(d.captured.capturedTime) + ''
      : null;
    var sinceText  = time ? ['since', time] : null;

    var linkedFields = ['fields', getPortalFields(guid).length];

    // collect and html-ify random data
    var randDetailsData = [];
    if (playerText && sinceText) {
      randDetailsData.push (playerText, sinceText);
    }

    randDetailsData.push (
      getRangeText(d), getEnergyText(d),
      linksText, getAvgResoDistText(d),
      linkedFields, getAttackApGainText(d),
      getHackDetailsText(d), getMitigationText(d)
    );

    // artifact details

    //niantic hard-code the fact it's just jarvis shards/targets - so until more examples exist, we'll do the same
    //(at some future point we can iterate through all the artifact types and add rows as needed)
    var jarvisArtifact = artifact.getPortalData (guid, 'jarvis');
    if (jarvisArtifact) {
      // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns
      var target = ['',''], shards = ['shards','(none)'];
      if (jarvisArtifact.target) {
        target = ['target', ''+(jarvisArtifact.target==TEAM_RES?'Resistance':'Enlightened')+''];
      }
      if (jarvisArtifact.fragments) {
        shards = [jarvisArtifact.fragments.length>1?'shards':'shard', '#'+jarvisArtifact.fragments.join(', #')];
      }

      randDetailsData.push (target, shards);
    }

    randDetails = '
' + genFourColumnTable(randDetailsData) + '
';

  }

  return randDetails;
}

// draws link-range and hack-range circles around the portal with the
// given details. Clear them if parameter 'd' is null.
window.setPortalIndicators = function(p) {

  if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
  portalRangeIndicator = null;
  if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
  portalAccessIndicator = null;

  // if we have a portal...

  if(p) {
    var coord = p.getLatLng();

    // range is only known for sure if we have portal details
    // TODO? render a min range guess until details are loaded..?

    var d = portalDetail.get(p.options.guid);
    if (d) {
      var range = getPortalRange(d);
      portalRangeIndicator = (range.range > 0
          ? L.geodesicCircle(coord, range.range, {
              fill: false,
              color: RANGE_INDICATOR_COLOR,
              weight: 3,
              dashArray: range.isLinkable ? undefined : "10,10",
              clickable: false })
          : L.circle(coord, range.range, { fill: false, stroke: false, clickable: false })
        ).addTo(map);
    }

    portalAccessIndicator = L.circle(coord, HACK_RANGE,
      { fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
    ).addTo(map);
  }

}

// highlights portal with given GUID. Automatically clears highlights
// on old selection. Returns false if the selected portal changed.
// Returns true if it's still the same portal that just needs an
// update.
window.selectPortal = function(guid) {
  var update = selectedPortal === guid;
  var oldPortalGuid = selectedPortal;
  selectedPortal = guid;

  var oldPortal = portals[oldPortalGuid];
  var newPortal = portals[guid];

  // Restore style of unselected portal
  if(!update && oldPortal) setMarkerStyle(oldPortal,false);

  // Change style of selected portal
  if(newPortal) {
    setMarkerStyle(newPortal, true);

    if (map.hasLayer(newPortal)) {
      newPortal.bringToFront();
    }
  }

  setPortalIndicators(newPortal);

  runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
  return update;
}
<commit_msg>don't try and request portal detailed data for a 'null' guid! oops<commit_after>// PORTAL DETAILS MAIN ///////////////////////////////////////////////
// main code block that renders the portal details in the sidebar and
// methods that highlight the portal in the map view.

window.renderPortalDetails = function(guid) {
  selectPortal(window.portals[guid] ? guid : null);

  if (guid && !portalDetail.isFresh(guid)) {
    portalDetail.request(guid);
  }

  // TODO? handle the case where we request data for a particular portal GUID, but it *isn't* in
  // window.portals....

  if(!window.portals[guid]) {
    urlPortal = guid;
    $('#portaldetails').html('');
    if(isSmartphone()) {
      $('.fullimg').remove();
      $('#mobileinfo').html('
tap here for info screen
');
    }
    return;
  }

  var portal = window.portals[guid];
  var data = portal.options.data;
  var details = portalDetail.get(guid);

  var modDetails = details ? '
'+getModDetails(details)+'
' : '';
  var miscDetails = details ? getPortalMiscDetails(guid,details) : '';
  var resoDetails = details ? getResonatorDetails(details) : '';

//TODO? other status details...
  var statusDetails = details ? '' : '
Loading details...
';

  var img = fixPortalImageUrl(details ? details.imageByUrl && details.imageByUrl.imageUrl : data.image);
  var title = details ? details.portalV2.descriptiveText.TITLE : data.title;

  var lat = data.latE6/1E6;
  var lng = data.lngE6/1E6;

  var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title;
  imgTitle += '\n\nClick to show full image.';
  var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined;

  var portalDetailedDescription = '';

  if(portalDetailObj) {
    portalDetailedDescription = '
';

    // TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them
    // (at this time the data isn't returned from the server - although a count of images IS returned!)

    if(portalDetailObj.submitter.name.length > 0) {
      if(portalDetailObj.submitter.team) {
        submitterSpan = '';
      } else {
        submitterSpan = '';
      }
      portalDetailedDescription += '
Photo by:	' + submitterSpan
                                + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
';
    }
    if(portalDetailObj.submitter.link.length > 0) {
      portalDetailedDescription += '
Photo from:	' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
';
    }

    if(portalDetailObj.description) {
      portalDetailedDescription += '
Description:	' + escapeHtmlSpecialChars(portalDetailObj.description) + '
';
    }
//    if(d.portalV2.descriptiveText.ADDRESS) {
//      portalDetailedDescription += '
Address:	' + escapeHtmlSpecialChars(d.portalV2.descriptiveText.ADDRESS) + '
';
//    }

    portalDetailedDescription += '
';
  }

  // portal level. start with basic data - then extend with fractional info in tooltip if available
  var levelInt = data ? data.level : getPortalLevel(details);
  var levelDetails = data.level;
  if (details) {
    levelDetails = getPortalLevel(details);
    if(levelDetails != 8) {
      if(levelDetails==Math.ceil(levelDetails))
        levelDetails += "\n8";
      else
        levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
      levelDetails += " resonator level(s) needed for next portal level";
    } else {
      levelDetails += "\nfully upgraded";
    }
  }
  levelDetails = "Level " + levelDetails;

  var linkDetails = [];

  var posOnClick = 'window.showPortalPosLinks('+lat+','+lng+',\''+escapeJavascriptString(title)+'\')';
  var permalinkUrl = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;

  if (typeof android !== 'undefined' && android && android.intentPosLink) {
    // android devices. one share link option - and the android app provides an interface to share the URL,
    // share as a geo: intent (navigation via google maps), etc

    var shareLink = $('
').html( $('').attr({onclick:posOnClick}).text('Share portal') ).html();
    linkDetails.push(''+shareLink+'');

  } else {
    // non-android - a permalink for the portal
    var permaHtml = $('
').html( $('').attr({href:permalinkUrl, target:'_blank', title:'Create a URL link to this portal'}).text('Portal link') ).html();
    linkDetails.push ( ''+permaHtml+'' );

    // and a map link popup dialog
    var mapHtml = $('
').html( $('').attr({onclick:posOnClick, title:'Link to alternative maps (Google, etc)'}).text('Map links') ).html();
    linkDetails.push(''+mapHtml+'');

  }

  $('#portaldetails')
    .html('') //to ensure it's clear
    .attr('class', TEAM_TO_CSS[portal.options.team])
    .append(
      $('
').attr({class:'title'}).text(data.title),

      $('').attr({class:'close', onclick:'renderPortalDetails(null); if(isSmartphone()) show("map");',title:'Close'}).text('X'),

      // help cursor via ".imgpreview img"
      $('
')
      .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"})
      .append(
        $('').attr({id:'level', title: levelDetails}).text(levelInt),
        $('
').attr({class:'portalDetails'}).html(portalDetailedDescription),
        $('').attr({class:'hide', src:img})
      ),

      modDetails,
      miscDetails,
      resoDetails,
      statusDetails,
      '
' + linkDetails.join('') + '
'
    );

  // only run the hooks when we have a portalDetails object - most plugins rely on the extended data
  // TODO? another hook to call always, for any plugins that can work with less data?
  if (details) {
    runHooks('portalDetailsUpdated', {guid: guid, portal: portal, portalDetails: details, portalData: data});
  }
}

window.getPortalMiscDetails = function(guid,d) {

  var randDetails;

  if (d) {

    // collect some random data that’s not worth to put in an own method
    var links = {incoming: 0, outgoing: 0};
    $.each(d.portalV2.linkedEdges||[], function(ind, link) {
      links[link.isOrigin ? 'outgoing' : 'incoming']++;
    });

    function linkExpl(t) { return '<tt title="↳ incoming links\n↴ outgoing links\n• is the portal">'+t+'</tt>'; }
    var linksText = [linkExpl('links'), linkExpl(' ↳ ' + links.incoming+'  •  '+links.outgoing+' ↴')];

    var player = d.captured && d.captured.capturingPlayerId
      ? '' + d.captured.capturingPlayerId + ''
      : null;
    var playerText = player ? ['owner', player] : null;

    var time = d.captured
      ? ''
        +  unixTimeToString(d.captured.capturedTime) + ''
      : null;
    var sinceText  = time ? ['since', time] : null;

    var linkedFields = ['fields', getPortalFields(guid).length];

    // collect and html-ify random data
    var randDetailsData = [];
    if (playerText && sinceText) {
      randDetailsData.push (playerText, sinceText);
    }

    randDetailsData.push (
      getRangeText(d), getEnergyText(d),
      linksText, getAvgResoDistText(d),
      linkedFields, getAttackApGainText(d),
      getHackDetailsText(d), getMitigationText(d)
    );

    // artifact details

    //niantic hard-code the fact it's just jarvis shards/targets - so until more examples exist, we'll do the same
    //(at some future point we can iterate through all the artifact types and add rows as needed)
    var jarvisArtifact = artifact.getPortalData (guid, 'jarvis');
    if (jarvisArtifact) {
      // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns
      var target = ['',''], shards = ['shards','(none)'];
      if (jarvisArtifact.target) {
        target = ['target', ''+(jarvisArtifact.target==TEAM_RES?'Resistance':'Enlightened')+''];
      }
      if (jarvisArtifact.fragments) {
        shards = [jarvisArtifact.fragments.length>1?'shards':'shard', '#'+jarvisArtifact.fragments.join(', #')];
      }

      randDetailsData.push (target, shards);
    }

    randDetails = '
' + genFourColumnTable(randDetailsData) + '
';

  }

  return randDetails;
}

// draws link-range and hack-range circles around the portal with the
// given details. Clear them if parameter 'd' is null.
window.setPortalIndicators = function(p) {

  if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
  portalRangeIndicator = null;
  if(portalAccessIndicator) map.removeLayer(portalAccessIndicator);
  portalAccessIndicator = null;

  // if we have a portal...

  if(p) {
    var coord = p.getLatLng();

    // range is only known for sure if we have portal details
    // TODO? render a min range guess until details are loaded..?

    var d = portalDetail.get(p.options.guid);
    if (d) {
      var range = getPortalRange(d);
      portalRangeIndicator = (range.range > 0
          ? L.geodesicCircle(coord, range.range, {
              fill: false,
              color: RANGE_INDICATOR_COLOR,
              weight: 3,
              dashArray: range.isLinkable ? undefined : "10,10",
              clickable: false })
          : L.circle(coord, range.range, { fill: false, stroke: false, clickable: false })
        ).addTo(map);
    }

    portalAccessIndicator = L.circle(coord, HACK_RANGE,
      { fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
    ).addTo(map);
  }

}

// highlights portal with given GUID. Automatically clears highlights
// on old selection. Returns false if the selected portal changed.
// Returns true if it's still the same portal that just needs an
// update.
window.selectPortal = function(guid) {
  var update = selectedPortal === guid;
  var oldPortalGuid = selectedPortal;
  selectedPortal = guid;

  var oldPortal = portals[oldPortalGuid];
  var newPortal = portals[guid];

  // Restore style of unselected portal
  if(!update && oldPortal) setMarkerStyle(oldPortal,false);

  // Change style of selected portal
  if(newPortal) {
    setMarkerStyle(newPortal, true);

    if (map.hasLayer(newPortal)) {
      newPortal.bringToFront();
    }
  }

  setPortalIndicators(newPortal);

  runHooks('portalSelected', {selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid});
  return update;
}
<|endoftext|>


<commit_before>/**
 * GitHub jQuery plugin
 *
 * @copyright (c)2005-2010, WDT Media Corp (http://wdtmedia.net)
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link http://github.com/jadb/jquery-tab-slider
 * @author jadb
 */
(function($){
	$.fn.github = function(args) {
		// available api calls
		var apis = {
			commits: 'http://github.com/api/v2/json/commits/list/%user%/%repo%/%branch%',
			repo: 'http://github.com/api/v2/json/repos/show/%user%/%repo%',
			repos: 'http://github.com/api/v2/json/repos/show/%user%',
			watched: 'http://github.com/api/v2/json/repos/watched/%user%',
		};
		// structure of data returned for every api call
		var data = {
			commits: ['author', 'authored_date', 'id', 'committed_date', 'committer', 'message', 'parents', 'tree', 'url'],
			repo: [],
			repos: ['description', 'fork', 'forks', 'homepage', 'name', 'open_issues', 'owner', 'url', 'watchers'],
			watched: ['description', 'fork', 'forks', 'homepage', 'name', 'open_issues', 'owner', 'url', 'watchers'],
		}
		// options
		var defaults = {
			api: 'repos',
			branch: 'master',
			max: 0,
			repo: null,
			filters: [],
			footer: '
Show all (%total%)
',
			forks: false,
			owner: true,
			tpl: '

%name%

%description%
Forks: %forks% | Watchers: %watchers% | Homepage: %homepage%

',
			user: null,
		};

		// object returned by API
		var obj = 'repositories';
		if ('commits' == args.api) {
			obj = 'commits';
			// overwrite default template when using `commits` API
			defaults.tpl = '
%author_name% on %authored_date%:
%message%

';
		}

		// merge args and defaults
		var opts = $.extend(true, defaults, args);

		var html = '';
		var container = this;

		// filter owned repos from watched ones
		if ('watched' == opts.api) {
			opts.owner = false;
			opts.tpl = (opts.tpl).replace('%name%', '%owner%/%name%');
		} else if ('forked' == opts.api) {
			opts.api = 'repos';
			opts.forks = true;
			opts.owner = false;
		}

		// build api's url
		var url = apis[opts.api].replace('%user%', opts.user).replace('%repo%', opts.repo).replace('%branch%', opts.branch);

		$.ajaxSetup({cache:true});
		$.getJSON(url, 'callback=?', function(response, textStatus) {

			$.each(response[obj], function(i, repo) {
				if ('commits' != opts.api) {
					if (
						// filter forks out
						(!opts.forks && true === repo.fork)
						// filter owned repos from watched ones
						|| (!opts.owner && opts.user === repo.owner && 'watched' == opts.api)
						// filter for only forked repos
						|| (!opts.owner && opts.forks && !repo.fork)
						) {
						return;
					}

					// regex filter repos by name
					for (var i in opts.filters) {
						if (typeof opts.filters[i] == 'string') {
							opts.filters[i] = new RegExp(opts.filters[i]);
						}
						if (repo.name.match(opts.filters[i], "i")) {
							return;
						}
					}
				}

				html = html + opts.tpl;
				$.each(data[opts.api], function(i, field) {
					var value = repo[field];
					if ('commits' == opts.api && ('author' == field || 'committer' == field)) {
						html = html.replace('%' + field + '_name%', value['name']);
						html = html.replace('%' + field + '_email%', value['email']);
					} else {
						if (typeof value == 'string') {
							value = value.replace(/</g, '<').replace(/>/g, '>');
						}
						html = html.replace('%' + field + '%', value);
					}
				});
			});
			container.html(html);

			// only if max limit is set
			if (opts.max > 0) {
				var total = 0;
				// loop through all container's children
				container.children().each(function(i) {
					if (i >= opts.max) {
						// hide the extra
						$(this).hide();
						// update total
						total = i;
					}
				});
				if (total > 0) {
					// add a `show all` link
					container.append((opts.footer).replace('%total%', total));
					// and add an onClick event to show all hidden children
					$(container.selector + ' *.footer').click(function() {
						container.children().each(function(i) {
							$(this).show();
						})
						// hide the `show all` link
						$(this).hide();
					});
					// add href to link
					$(container.selector + ' *.footer a').attr('href', container.selector);
				}
			}
		});
	}
})(jQuery);<commit_msg>ADD: enabled gravatar using MD5 js library<commit_after>/**
 * GitHub jQuery plugin
 *
 * @copyright (c)2005-2010, WDT Media Corp (http://wdtmedia.net)
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link http://github.com/jadb/jquery-tab-slider
 * @author jadb
 */
(function($){
	$.fn.github = function(args) {
		// available api calls
		var apis = {
			commits: 'http://github.com/api/v2/json/commits/list/%user%/%repo%/%branch%',
			repo: 'http://github.com/api/v2/json/repos/show/%user%/%repo%',
			repos: 'http://github.com/api/v2/json/repos/show/%user%',
			watched: 'http://github.com/api/v2/json/repos/watched/%user%',
		};
		// structure of data returned for every api call
		var data = {
			commits: ['author', 'authored_date', 'id', 'committed_date', 'committer', 'message', 'parents', 'tree', 'url'],
			repo: [],
			repos: ['description', 'fork', 'forks', 'homepage', 'name', 'open_issues', 'owner', 'url', 'watchers'],
			watched: ['description', 'fork', 'forks', 'homepage', 'name', 'open_issues', 'owner', 'url', 'watchers'],
		}
		// options
		var defaults = {
			api: 'repos',
			branch: 'master',
			gravatar: false,
			max: 0,
			repo: null,
			filters: [],
			footer: '
Show all (%total%)
',
			forks: false,
			owner: true,
			tpl: '

%name%

%description%
Forks: %forks% | Watchers: %watchers% | Homepage: %homepage%

',
			user: null,
		};

		// object returned by API
		var obj = 'repositories';
		if ('commits' == args.api) {
			obj = 'commits';
			// enable gravatar by default
			defaults.gravatar = true;
			// overwrite default template when using `commits` API
			defaults.tpl = '
%author_name% on %authored_date%:
%message%

';
		}

		// merge args and defaults
		var opts = $.extend(true, defaults, args);

		var html = '';
		var container = this;

		// filter owned repos from watched ones
		if ('watched' == opts.api) {
			opts.owner = false;
			opts.tpl = (opts.tpl).replace('%name%', '%owner%/%name%');
		} else if ('forked' == opts.api) {
			opts.api = 'repos';
			opts.forks = true;
			opts.owner = false;
		}

		// build api's url
		var url = apis[opts.api].replace('%user%', opts.user).replace('%repo%', opts.repo).replace('%branch%', opts.branch);

		$.ajaxSetup({cache:true});
		$.getJSON(url, 'callback=?', function(response, textStatus) {

			$.each(response[obj], function(i, repo) {
				if ('commits' != opts.api) {
					if (
						// filter forks out
						(!opts.forks && true === repo.fork)
						// filter owned repos from watched ones
						|| (!opts.owner && opts.user === repo.owner && 'watched' == opts.api)
						// filter for only forked repos
						|| (!opts.owner && opts.forks && !repo.fork)
						) {
						return;
					}

					// regex filter repos by name
					for (var i in opts.filters) {
						if (typeof opts.filters[i] == 'string') {
							opts.filters[i] = new RegExp(opts.filters[i]);
						}
						if (repo.name.match(opts.filters[i], "i")) {
							return;
						}
					}
				}

				html = html + opts.tpl;
				$.each(data[opts.api], function(i, field) {
					var value = repo[field];
					if ('commits' == opts.api && ('author' == field || 'committer' == field)) {
						html = html.replace('%' + field + '_name%', value['name']);
						html = html.replace('%' + field + '_email%', value['email']);
						if (opts.gravatar) {
							html = html.replace('%gravatar%', 'http://www.gravatar.com/avatar/' + MD5.hexdigest(value['email']) + '?s=60');
						}
					} else {
						if (typeof value == 'string') {
							value = value.replace(/</g, '<').replace(/>/g, '>');
						}
						html = html.replace('%' + field + '%', value);
					}
				});
			});
			container.html(html);

			// only if max limit is set
			if (opts.max > 0) {
				var total = 0;
				// loop through all container's children
				container.children().each(function(i) {
					if (i >= opts.max) {
						// hide the extra
						$(this).hide();
						// update total
						total = i;
					}
				});
				if (total > 0) {
					// add a `show all` link
					container.append((opts.footer).replace('%total%', total));
					// and add an onClick event to show all hidden children
					$(container.selector + ' *.footer').click(function() {
						container.children().each(function(i) {
							$(this).show();
						})
						// hide the `show all` link
						$(this).hide();
					});
					// add href to link
					$(container.selector + ' *.footer a').attr('href', container.selector);
				}
			}
		});
	}
})(jQuery);

/* md5.js - MD5 Message-Digest
 * Copyright (C) 1999,2002 Masanao Izumo <iz@onicos.co.jp>
 * Version: 2.0.0
 * LastModified: May 13 2002
 *
 * This program is free software.  You can redistribute it and/or modify
 * it without any warranty.  This library calculates the MD5 based on RFC1321.
 * See RFC1321 for more information and algorism.
 */
var MD5={};(function(){var F=[0,3614090360,3905402710,606105819,3250441966,4118548399,1200080426,2821735955,4249261313,1770035416,2336552879,4294925233,2304563134,1804603682,4254626195,2792965006,1236535329,4129170786,3225465664,643717713,3921069994,3593408605,38016083,3634488961,3889429448,568446438,3275163606,4107603335,1163531501,2850285829,4243563512,1735328473,2368359562,4294588738,2272392833,1839030562,4259657740,2763975236,1272893353,4139469664,3200236656,681279174,3936430074,3572445317,76029189,3654602809,3873151461,530742520,3299628645,4096336452,1126891415,2878612391,4237533241,1700485571,2399980690,4293915773,2240044497,1873313359,4264355552,2734768916,1309151649,4149444226,3174756917,718787259,3951481745];var E=[[0,7,1],[1,12,2],[2,17,3],[3,22,4],[4,7,5],[5,12,6],[6,17,7],[7,22,8],[8,7,9],[9,12,10],[10,17,11],[11,22,12],[12,7,13],[13,12,14],[14,17,15],[15,22,16]];var D=[[1,5,17],[6,9,18],[11,14,19],[0,20,20],[5,5,21],[10,9,22],[15,14,23],[4,20,24],[9,5,25],[14,9,26],[3,14,27],[8,20,28],[13,5,29],[2,9,30],[7,14,31],[12,20,32]];var C=[[5,4,33],[8,11,34],[11,16,35],[14,23,36],[1,4,37],[4,11,38],[7,16,39],[10,23,40],[13,4,41],[0,11,42],[3,16,43],[6,23,44],[9,4,45],[12,11,46],[15,16,47],[2,23,48]];var B=[[0,6,49],[7,10,50],[14,15,51],[5,21,52],[12,6,53],[3,10,54],[10,15,55],[1,21,56],[8,6,57],[15,10,58],[6,15,59],[13,21,60],[4,6,61],[11,10,62],[2,15,63],[9,21,64]];function N(O,Q,P){return(O&Q)|(~O&P)}function M(O,Q,P){return(O&P)|(Q&~P)}function L(O,Q,P){return O^Q^P}function J(O,Q,P){return Q^(O|~P)}var A=[[N,E],[M,D],[L,C],[J,B]];function G(O){return String.fromCharCode(O&255)+String.fromCharCode((O>>>8)&255)+String.fromCharCode((O>>>16)&255)+String.fromCharCode((O>>>24)&255)}function H(O){return O.charCodeAt(0)|(O.charCodeAt(1)<<8)|(O.charCodeAt(2)<<16)|(O.charCodeAt(3)<<24)}function K(O){while(O<0){O+=4294967296}while(O>4294967295){O-=4294967296}return O}function I(W,g,R,Q,O){var V,U,T,S;var P,e,Y;var Z,X;V=Q[0];U=Q[1];T=Q[2];S=Q[3];P=O[0];e=O[1];Y=O[2];X=R(g[U],g[T],g[S]);Z=g[V]+X+W[P]+F[Y];Z=K(Z);Z=((Z<<e)|(Z>>>(32-e)));Z+=g[U];g[V]=K(Z)}MD5.digest=function(V){var Q,a,P,b;var Z,Y,W,X,O;var U,S,R;var T;P=[1732584193,4023233417,2562383102,271733878];Z=V.length;Y=Z&63;W=(Y<56)?(56-Y):(120-Y);if(W>0){V+="\x80";for(U=0;U<W-1;U++){V+="\x00"}}V+=G(Z*8);V+=G(0);Z+=W+8;Q=[0,1,2,3];a=[16];b=[4];for(R=0;R<Z;R+=64){for(U=0,S=R;U<16;U++,S+=4){a[U]=V.charCodeAt(S)|(V.charCodeAt(S+1)<<8)|(V.charCodeAt(S+2)<<16)|(V.charCodeAt(S+3)<<24)}for(U=0;U<4;U++){b[U]=P[U]}for(U=0;U<4;U++){X=A[U][0];O=A[U][1];for(S=0;S<16;S++){I(a,b,X,Q,O[S]);T=Q[0];Q[0]=Q[3];Q[3]=Q[2];Q[2]=Q[1];Q[1]=T}}for(U=0;U<4;U++){P[U]+=b[U];P[U]=K(P[U])}}return G(P[0])+G(P[1])+G(P[2])+G(P[3])};MD5.hexdigest=function(R){var Q,P,S;var O;O=MD5.digest(R);P="";for(Q=0;Q<16;Q++){S=O.charCodeAt(Q);P+="0123456789abcdef".charAt((S>>4)&15);P+="0123456789abcdef".charAt(S&15)}return P}})();<|endoftext|>