<commit_before>var path = require('path')
  , Matcher
  , rule
  , Rule;

// Define a helper object with some utility functions
Matcher = new (function () {
  // Split a task to two parts, name space and task name.
  // For example, given 'foo:bin/a%.c', return an object with
  // - 'ns'     : foo
  // - 'name'   : bin/a%.c
  this.split = function(task) {
    var parts = task.split(':')
    , name  = parts.pop()
    , ns    = Matcher.resolveNS( parts );
    return {
      'name' : name,
      'ns'   : ns
    };
  }

  // Return the namespace based on an array of names.
  // For example, given ['foo', 'baz' ], return the namespace
  //
  //   default -> foo -> baz
  //
  // where default is the global root namespace
  // and -> means child namespace.
  this.resolveNS = function(parts) {
    var  ns    = jake.defaultNamespace;
    for(var i = 0, l = parts.length; ns && i < l; i++) {
      ns = ns.childNamespaces[parts[i]];
    }
    return ns;
  };

  // Given a pattern p, say 'foo:bin/a%.c'
  // Return an object with
  // - 'ns'     : foo
  // - 'dir'    : bin
  // - 'prefix' : a
  // - 'suffix' : .c
  this.resolve = function(p) {
    var task = Matcher.split(p),
        name  = task.name,
        ns    = task.ns;
    var split = path.basename(name).split('%');
    return {
      'ns'  : ns,
      'dir' : path.dirname(name),
      'prefix' : split[0],
      'suffix' : split[1],
    };
  }

  // Test whether string a is a suffix of string b
  this.stringEndWith = function (a,b) {
    var l;
    return (l = b.lastIndexOf(a)) == -1 ? false : l + a.length == b.length;
  }

  // Replace the suffix a of the string s with b.
  // Note that, it is assumed a is a suffix of s.
  this.stringReplaceSuffix = function (s,a,b) {
    return s.slice(0,s.lastIndexOf(a)) + b;
  }

  // Test wether the a prerequisite matchs the pattern.
  // The arg 'pattern' does not have namespace as prefix.
  // For example, the following tests are true
  //
  //   pattern      |    prereq
  //   bin/%.o      |    bin/main.o
  //   bin/%.o      |    foo:bin/main.o
  //
  // The following tests are false (trivally)
  //
  //   pattern      |    prereq
  //   bin/%.o      |    foobin/main.o
  //   bin/%.o      |    bin/main.oo

  this.match = function(pattern, prereq) {
    if (pattern.indexOf('%') == -1) {
      // No Pattern. No Folder. No Namespace.
      // A Simple Suffix Rule. Just test suffix
      return this.stringEndWith(pattern, prereq);
    } else {
      // Resolve the dir, prefix and suffix of pattern
      var p = this.resolve(pattern);

      // Resolve the namespace and task name of prereq
      var task = this.split(prereq),
          name  = task.name,
          ns    = task.ns;

      // Set the objective as the task name of prereq
      var obj = name;

      // Namespace is already matched.

      // Check dir
      if (path.dirname(obj) != p.dir)
        return false;

      var filename = path.basename(obj);

      // Check file name length.
      if (p.prefix.length + p.suffix.length + 1
            > filename.length) {
        // Length does not match.
        return false;
      }

      // Check prefix
      if (filename.indexOf(p.prefix) != 0) {
        return false;
      }

      // Check suffix
      if (! this.stringEndWith( p.suffix, filename)) {
        return false;
      }

      // OK. Find a match.
      return true;
    }
  }

  // Generate the source based on
  //  - prereq   prerequisite
  //  - objP    pattern for the objective
  //  - srcP    pattern for the source
  //
  // Return the source with properties
  //  - dep      the prerequisite of source
  //             (with the namespace)
  //
  //  - file     the file name of source
  //             (without the namespace)
  //
  // For example, given
  //
  //  - prereq   foo:bin/main.o
  //  - objP    bin/%.o
  //  - srcP    src/%.c
  //
  // return {
  //    'dep' : 'foo:src/main.c',
  //    'file': 'src/main.c'
  //  };
  //
  this.source = function(prereq, objP, srcP) {
    if (objP.indexOf('%') == -1) {
      // A Simple Suffix
      var src = this.stringReplaceSuffix( prereq, objP, srcP );
      return {
        'dep'  : src
      , 'file' : src
      };

    } else {
      // Resolve the dir, prefix and suffix of pattern
      var p = this.resolve(objP);

      // Resolve the namespace and task name
      var parts = prereq.split(':')
        , obj   = parts.pop()
        , ns    = this.resolveNS( parts );

      var filename = path.basename(obj);

      // Resolve the matching pattern for the symbol '%'
      var fill = filename.slice(
        p.prefix.length,
        filename.length - p.suffix.length
      );

      // Replace the % with the mathcing pattern
      // found in last step
      var src = srcP.replace('%', fill);

      // Construct the return object
      if (parts.length == 0) {
        // no name space
        return {
          'dep'  : src
        , 'file' : src };
      } else {
        // Add namespace to the prerequisite of source file
        var dep = parts.join(':') + ':' + src;
        return {
            'dep'  : dep
          , 'file' : src
        };
      }
    }
  }

})();

Rule = function (opts) {
  this.args = opts.args;
  this.desc =  opts.desc;
  this.ns = opts.ns;
};

rule = new (function () {
  // The steps are
  // 1. Resolve the task name and namespace for the prerequisite.
  // 2. Fetch all the rules belonging to the namespace.
  // 3. Test each pattern.
  this.match = function(prereq, args) {
    // 1.
    var task = Matcher.split(prereq),
       name  = task.name,
       ns    = task.ns;

    if (ns == null) return null;

    // 2.
    var rules = ns.rules;

    // 3.
    for (var p in rules) {
      if (rules.hasOwnProperty(p)
          && typeof rules[p] == 'object'
          && Matcher.match(p, prereq)
        ) {
          return p;
        }
    }
    return null;
  }

  // Create a file task based on the Rule.
  this.createTask = function(pattern, prereq, args) {
    var objP
    , srcP
    , prereqs = []
    , action
    , opts;

    // 1. Resolve the task name and namespace for the prerequisite.
    var task = Matcher.split(prereq)
    ,  name  = task.name
    ,  ns    = task.ns;

    // 2. Obtain the matching rule.
    var rule = ns.rules[pattern].args.slice(0);  // We need a copy.

    // 3. Obtain the pattern for the objective and source.
    objP = rule.shift();
    srcP = rule.shift();

    // 4. Resolve the prerequisite and file name representing the source
    var src = Matcher.source( prereq, objP, srcP );
    // - src.dep  is the prerequisite with namespace.
    // - src.file is the source file name.

    // 5. Generate the prerequisite for the matching task.
    //    It is the original prerequisites plus the prerequisite
    //    representing source file, i.e.,
    //
    //      rule( '%.o', '%.c', ['some.h'] ...
    //
    //    If the objective is main.o, then new task should be
    //
    //      file( 'main.o', ['main.c', 'some.h' ] ...
    if (Array.isArray(rule[0])) {
      prereqs = rule.shift().slice(0); // again, a copy
    }
    prereqs.unshift( src.dep );

    // 6. Fetch the action and opts.
    while ((arg = rule.shift())) {
      if (typeof arg == 'function') {
        action = arg;
      }
      else {
        opts = arg;
      }
    }

    // 7. Create a new action with obj and src replaced.
    var newAction = function() {
      action(name, src.file, args);
    };

    // 8. Now, create the file task.
    var newArgs = [name, prereqs, newAction, opts];
    newArgs.unshift('file');

    // 9. Insert the file task into Jake
    //
    // Since createTask function stores the task as a child task
    // of currentNamespace. Here we temporariliy switch the namespace.
    var tNs = jake.currentNamespace;
    jake.currentNamespace = ns;
    var createdTask = jake.createTask.apply(global, newArgs);
    jake.currentNamespace = tNs;

    return createdTask;
  };

})();

rule.Rule = Rule;

module.exports = rule;
<commit_msg>Begin linting, added semicolons<commit_after>var path = require('path')
  , Matcher
  , rule
  , Rule;

// Define a helper object with some utility functions
Matcher = new (function () {
  // Split a task to two parts, name space and task name.
  // For example, given 'foo:bin/a%.c', return an object with
  // - 'ns'     : foo
  // - 'name'   : bin/a%.c
  this.split = function(task) {
    var parts = task.split(':')
    , name  = parts.pop()
    , ns    = Matcher.resolveNS( parts );
    return {
      'name' : name,
      'ns'   : ns
    };
  };

  // Return the namespace based on an array of names.
  // For example, given ['foo', 'baz' ], return the namespace
  //
  //   default -> foo -> baz
  //
  // where default is the global root namespace
  // and -> means child namespace.
  this.resolveNS = function(parts) {
    var  ns    = jake.defaultNamespace;
    for(var i = 0, l = parts.length; ns && i < l; i++) {
      ns = ns.childNamespaces[parts[i]];
    }
    return ns;
  };

  // Given a pattern p, say 'foo:bin/a%.c'
  // Return an object with
  // - 'ns'     : foo
  // - 'dir'    : bin
  // - 'prefix' : a
  // - 'suffix' : .c
  this.resolve = function(p) {
    var task = Matcher.split(p),
        name  = task.name,
        ns    = task.ns;
    var split = path.basename(name).split('%');
    return {
      ns: ns
    , dir: path.dirname(name)
    , prefix: split[0]
    , suffix: split[1]
    };
  };

  // Test whether string a is a suffix of string b
  this.stringEndWith = function (a,b) {
    var l;
    return (l = b.lastIndexOf(a)) == -1 ? false : l + a.length == b.length;
  };

  // Replace the suffix a of the string s with b.
  // Note that, it is assumed a is a suffix of s.
  this.stringReplaceSuffix = function (s,a,b) {
    return s.slice(0,s.lastIndexOf(a)) + b;
  };

  // Test wether the a prerequisite matchs the pattern.
  // The arg 'pattern' does not have namespace as prefix.
  // For example, the following tests are true
  //
  //   pattern      |    prereq
  //   bin/%.o      |    bin/main.o
  //   bin/%.o      |    foo:bin/main.o
  //
  // The following tests are false (trivally)
  //
  //   pattern      |    prereq
  //   bin/%.o      |    foobin/main.o
  //   bin/%.o      |    bin/main.oo

  this.match = function(pattern, prereq) {
    if (pattern.indexOf('%') == -1) {
      // No Pattern. No Folder. No Namespace.
      // A Simple Suffix Rule. Just test suffix
      return this.stringEndWith(pattern, prereq);
    } else {
      // Resolve the dir, prefix and suffix of pattern
      var p = this.resolve(pattern);

      // Resolve the namespace and task name of prereq
      var task = this.split(prereq),
          name  = task.name,
          ns    = task.ns;

      // Set the objective as the task name of prereq
      var obj = name;

      // Namespace is already matched.

      // Check dir
      if (path.dirname(obj) != p.dir) {
        return false;
      }

      var filename = path.basename(obj);

      // Check file name length.
      if ((p.prefix.length + p.suffix.length + 1) > filename.length) {
        // Length does not match.
        return false;
      }

      // Check prefix
      if (filename.indexOf(p.prefix) !== 0) {
        return false;
      }

      // Check suffix
      if (! this.stringEndWith( p.suffix, filename)) {
        return false;
      }

      // OK. Find a match.
      return true;
    }
  };

  // Generate the source based on
  //  - prereq   prerequisite
  //  - objP    pattern for the objective
  //  - srcP    pattern for the source
  //
  // Return the source with properties
  //  - dep      the prerequisite of source
  //             (with the namespace)
  //
  //  - file     the file name of source
  //             (without the namespace)
  //
  // For example, given
  //
  //  - prereq   foo:bin/main.o
  //  - objP    bin/%.o
  //  - srcP    src/%.c
  //
  // return {
  //    'dep' : 'foo:src/main.c',
  //    'file': 'src/main.c'
  //  };
  //
  this.source = function(prereq, objP, srcP) {
    if (objP.indexOf('%') == -1) {
      // A Simple Suffix
      var src = this.stringReplaceSuffix( prereq, objP, srcP );
      return {
        'dep'  : src
      , 'file' : src
      };

    } else {
      // Resolve the dir, prefix and suffix of pattern
      var p = this.resolve(objP);

      // Resolve the namespace and task name
      var parts = prereq.split(':')
        , obj   = parts.pop()
        , ns    = this.resolveNS( parts );

      var filename = path.basename(obj);

      // Resolve the matching pattern for the symbol '%'
      var fill = filename.slice(
        p.prefix.length,
        filename.length - p.suffix.length
      );

      // Replace the % with the mathcing pattern
      // found in last step
      var src = srcP.replace('%', fill);

      // Construct the return object
      if (parts.length == 0) {
        // no name space
        return {
          'dep'  : src
        , 'file' : src };
      } else {
        // Add namespace to the prerequisite of source file
        var dep = parts.join(':') + ':' + src;
        return {
            'dep'  : dep
          , 'file' : src
        };
      }
    }
  };

})();

Rule = function (opts) {
  this.args = opts.args;
  this.desc =  opts.desc;
  this.ns = opts.ns;
};

rule = new (function () {
  // The steps are
  // 1. Resolve the task name and namespace for the prerequisite.
  // 2. Fetch all the rules belonging to the namespace.
  // 3. Test each pattern.
  this.match = function(prereq, args) {
    // 1.
    var task = Matcher.split(prereq),
       name  = task.name,
       ns    = task.ns;

    if (ns == null) return null;

    // 2.
    var rules = ns.rules;

    // 3.
    for (var p in rules) {
      if (rules.hasOwnProperty(p)
          && typeof rules[p] == 'object'
          && Matcher.match(p, prereq)
        ) {
          return p;
        }
    }
    return null;
  }

  // Create a file task based on the Rule.
  this.createTask = function(pattern, prereq, args) {
    var objP
    , srcP
    , prereqs = []
    , action
    , opts;

    // 1. Resolve the task name and namespace for the prerequisite.
    var task = Matcher.split(prereq)
    ,  name  = task.name
    ,  ns    = task.ns;

    // 2. Obtain the matching rule.
    var rule = ns.rules[pattern].args.slice(0);  // We need a copy.

    // 3. Obtain the pattern for the objective and source.
    objP = rule.shift();
    srcP = rule.shift();

    // 4. Resolve the prerequisite and file name representing the source
    var src = Matcher.source( prereq, objP, srcP );
    // - src.dep  is the prerequisite with namespace.
    // - src.file is the source file name.

    // 5. Generate the prerequisite for the matching task.
    //    It is the original prerequisites plus the prerequisite
    //    representing source file, i.e.,
    //
    //      rule( '%.o', '%.c', ['some.h'] ...
    //
    //    If the objective is main.o, then new task should be
    //
    //      file( 'main.o', ['main.c', 'some.h' ] ...
    if (Array.isArray(rule[0])) {
      prereqs = rule.shift().slice(0); // again, a copy
    }
    prereqs.unshift( src.dep );

    // 6. Fetch the action and opts.
    while ((arg = rule.shift())) {
      if (typeof arg == 'function') {
        action = arg;
      }
      else {
        opts = arg;
      }
    }

    // 7. Create a new action with obj and src replaced.
    var newAction = function() {
      action(name, src.file, args);
    };

    // 8. Now, create the file task.
    var newArgs = [name, prereqs, newAction, opts];
    newArgs.unshift('file');

    // 9. Insert the file task into Jake
    //
    // Since createTask function stores the task as a child task
    // of currentNamespace. Here we temporariliy switch the namespace.
    var tNs = jake.currentNamespace;
    jake.currentNamespace = ns;
    var createdTask = jake.createTask.apply(global, newArgs);
    jake.currentNamespace = tNs;

    return createdTask;
  };

})();

rule.Rule = Rule;

module.exports = rule;
<|endoftext|>


<commit_before>var captionLength = 0;
var captionOptions = ["Oh, hello there.", "So this is my page.", "Hope you get what you need :)"]
var captionIndex = 0;

var rgbOptions = ["rgb(61, 183, 88)", "rgb(230, 79, 59)", "rgb(222, 77, 99)", "rgb(211, 188, 180)", "rgb(255, 255, 255)", "rgb(255, 220, 90)", "rgb(77, 222, 171)"]
var colorIndex = 0;

function runCaptions() {
  cursorAnimation();
  $caption = $('#caption');
  setTimeout('showCaptions()', 1000);
}

function cursorAnimation() {
  $('#cursor').animate({
      opacity: 0
  }, 400).animate({
      opacity: 1
  }, 400);
  setTimeout('cursorAnimation()')
}

function type() {
    $caption.html(captionToType.substr(0, captionLength++));
    if(captionLength < captionToType.length+1) {
        setTimeout('type()', 70);
    }
}

function erase() {
    $caption.html(captionToType.substr(0, captionLength--));
    if(captionLength >= 0) {
        setTimeout('erase()', 50);
    }
}

function showCaptions() {
  captionToType = captionOptions[captionIndex];
  type();
  if (captionIndex < (captionOptions.length - 1)) {
    captionIndex++
    setTimeout('erase()', 4000);
    setTimeout('showCaptions()', 6000)
  } else {
    setTimeout(function(){
      $('#cursor').remove()
    }, 1500)
  }
}

function toggleMethods() {
  $('.methods').toggle('slide');
}

function raveTime() {
  if (colorIndex == rgbOptions.length) {
    colorIndex = 0;
  }
  $('html').animate({"background-color": rgbOptions[colorIndex]}, 60);
  colorIndex++
  setTimeout('raveTime()', 100);
}

function play(){
  document.getElementById("audio").play();
}

$(document).ready(function(){
  runCaptions();

  $('.message a').on('click', function(e) {
    e.preventDefault()
    toggleMethods();
  })

  $('.color-change p.background').on('click', function(e) {
    e.preventDefault();
    if (colorIndex == rgbOptions.length) {
      colorIndex = 0;
    }
    $('html').animate({"background-color": rgbOptions[colorIndex]})
    colorIndex++
  })

  $('.rave').on('click', function(e){
    if ($(this).html() == "Party Time") {
      e.preventDefault();
      raveTime(colorIndex);
      $(this).html("Boring Time");
      play();
    }
  })
});
<commit_msg>lots of refactor<commit_after>var captionLength = 0;
var captionOptions = ["Oh, hello there.", "So this is my page.", "Hope you get what you need :)"]
var captionIndex = 0;
var rgbOptions = ["rgb(61, 183, 88)", "rgb(230, 79, 59)", "rgb(222, 77, 99)", "rgb(211, 188, 180)", "rgb(255, 255, 255)", "rgb(255, 220, 90)", "rgb(77, 222, 171)"]
var colorIndex = 0;
var currentlyRaving = false;

function runCaptions() {
  cursorAnimation();
  $caption = $('#caption');
  setTimeout('showCaptions()', 1000);
}

function cursorAnimation() {
  $('#cursor').animate({
      opacity: 0
  }, 400).animate({
      opacity: 1
  }, 400);
  setTimeout('cursorAnimation()')
}

function type() {
    $caption.html(captionToType.substr(0, captionLength++));
    if(captionLength < captionToType.length+1) {
        setTimeout('type()', 70);
    }
}

function erase() {
    $caption.html(captionToType.substr(0, captionLength--));
    if(captionLength >= 0) {
        setTimeout('erase()', 50);
    }
}

function showCaptions() {
  captionToType = captionOptions[captionIndex];
  type();
  if (captionIndex < (captionOptions.length - 1)) {
    captionIndex++
    setTimeout('erase()', 4000);
    setTimeout('showCaptions()', 6000)
  } else {
    setTimeout(function(){
      $('#cursor').remove()
    }, 1500)
  }
}

function toggleMethods() {
  $('.methods').toggle('slide');
}

function switchDomText() {
  var $domText = $('.rave').html();
  if ($domText == "Boring Time") {
    $('.rave').html('Party Time')
  } else {
    $('.rave').html('Boring Time')
  }
}

function switchRaveState() {
  if (currentlyRaving) {
    currentlyRaving = false
  } else {
    currentlyRaving = true
  }
}

function checkRaveState() {
  if (currentlyRaving) {
    animateBackgroundColor(60);
    play();
    setTimeout('checkRaveState()', 100);
  } else {
    stopAndReset();
  }
}

function animateBackgroundColor(speed) {
  if (colorIndex == rgbOptions.length) {
    colorIndex = 0;
  }
  $('html').animate({"background-color": rgbOptions[colorIndex]}, speed);
  colorIndex++
}

function play() {
  var audio = $("#audio")
  // if (audio.paused) {
    $("#audio").play();
  // }
}

function stopAndReset() {
  var audio = $("#audio")
  audio.stop();
  audio.currentTime = 0
}

$(function(){
  runCaptions();

  $('.message a').on('click', function(e) {
    e.preventDefault()
    toggleMethods();
  })

  $('.color-change p.background').on('click', function(e) {
    e.preventDefault();
    animateBackgroundColor(350)
  })

  $('.rave').on('click', function(e){
    e.preventDefault();
    switchRaveState();
    switchDomText();
    checkRaveState();
  })
});
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var Post = require('./post');
var Category = require('./category');
var Tag = require('./tag');
var Media = require('./media');
var Comment = require('./comment');
var Follow = require('./follow');
var request = require('./util/request');
var debug = require('debug')('wpcom:site');

/**
 * Resources array
 * A list of endpoints with the same structure
 */

var resources = [
  'categories',
  'comments',
  'follows',
  'media',
  'posts',
  [ 'stats', 'stats' ],
  [ 'statsVisits', 'stats/visits' ],
  [ 'statsReferrers', 'stats/referrers' ],
  [ 'statsTopPosts', 'stats/top-posts' ],
  [ 'statsCountryViews', 'stats/country-views' ],
  [ 'statsClicks', 'stats/clicks' ],
  [ 'statsSearchTerms', 'stats/search-terms' ],
  'tags',
  'users'
];

/**
 * Create a Site instance
 *
 * @param {WPCOM} wpcom
 * @api public
 */

function Site(id, wpcom) {
  if (!(this instanceof Site)) {
    return new Site(id, wpcom);
  }

  this.wpcom = wpcom;

  debug('set %o site id', id);
  this._id = encodeURIComponent(id);
}

/**
 * Require site information
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Site.prototype.get = function (query, fn) {
  return request.get(this.wpcom, null, '/sites/' + this._id, query, fn);
};

/**
 * List method builder
 *
 * @param {String} subpath
 * @param {Function}
 * @api private
 */

function list (subpath, apiVersion) {

  /**
   * Return the <names>List method
   *
   * @param {Object} [query]
   * @param {Function} fn
   * @api public
   */

  return function (query, fn) {
    var path = '/sites/' + this._id + '/' + subpath;
    return request.get(this.wpcom, { apiVersion: apiVersion }, path, query, fn);
  };
}

// walk for each resource and create related method
var i, res, isarr, name, subpath, apiVersion;
for (i = 0; i < resources.length; i++) {
  res = resources[i];
  isarr = Array.isArray(res);

  name =  isarr ? res[0] : res + 'List';
  subpath = isarr ? res[1] : res;
  apiVersion = isarr && 'string' === typeof res[2] ? res[2] : '1';

  debug('adding %o method in %o sub-path (v%o)', 'site.' + name + '()', subpath, apiVersion);
  Site.prototype[name] = list(subpath, apiVersion);
}

/**
 * :POST:
 * Create a `Post` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.post = function (id) {
  return new Post(id, this._id, this.wpcom);
};

/**
 * :POST:
 * Add a new blog post
 *
 * @param {Object} body
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addPost = function (body, fn) {
  var post = new Post(null, this._id, this.wpcom);
  return post.add(body, fn);
};

/**
 * :POST:
 * Delete a blog post
 *
 * @param {String} id
 * @param {Function} fn
 * @return {Post} remove Post instance
 */

Site.prototype.deletePost = function (id, fn) {
  var post = new Post(id, this._id, this.wpcom);
  return post.delete(fn);
};

/**
 * Create a `Media` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.media = function (id) {
  return new Media(id, this._id, this.wpcom);
};

/**
 * Add a media from a file
 *
 * @param {Object} [query]
 * @param {Array|String} files
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addMediaFiles = function (query, files, fn) {
  var media = new Media(null, this._id, this.wpcom);
  return media.addFiles(query, files, fn);
};

/**
 * Add a new media from url
 *
 * @param {Object} [query]
 * @param {Array|String} files
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addMediaUrls = function (query, files, fn) {
  var media = new Media(null, this._id, this.wpcom);
  return media.addUrls(query, files, fn);
};

/**
 * Delete a blog media
 *
 * @param {String} id
 * @param {Function} fn
 * @return {Post} removed Media instance
 */

Site.prototype.deleteMedia = function (id, fn) {
  var media = new Media(id, this._id, this.wpcom);
  return media.del(fn);
};

/**
 * Create a `Comment` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.comment = function (id) {
  return new Comment(id, null, this._id, this.wpcom);
};

/**
 * Create a `Follow` instance
 *
 * @api public
 */

Site.prototype.follow = function () {
  return new Follow(this._id, this.wpcom);
};

/**
 * Create a `Category` instance
 * Set `cat` alias
 *
 * @param {String} [slug]
 * @api public
 */

Site.prototype.cat = Site.prototype.category = function (slug) {
  return new Category(slug, this._id, this.wpcom);
};

/**
 * Create a `Tag` instance
 *
 * @param {String} [slug]
 * @api public
 */

Site.prototype.tag = function (slug) {
  return new Tag(slug, this._id, this.wpcom);
};

/**
 * Get a list of shortcodes available on a site.
 *
 * Note: The current user must have publishing access.
 *
 * @api public
 */

Site.prototype.shortcodes = function (query, fn) {
  var path = '/sites/' + this._id + '/shortcodes';
  return request.get(this.wpcom, null, path, query, fn);
};

/**
 * Get a rendered shortcode for a site.
 *
 * Note: The current user must have publishing access.
 *
 * @param {String} shortcode
 * @api public
 */

Site.prototype.renderShortcode = function (shortcode, query, fn) {
  if ('string' !== typeof shortcode) {
    throw new TypeError('expected a shortcode String');
  }

  var path = '/sites/' + this._id + '/shortcodes/render';
  return request.get(this.wpcom, { shortcode: shortcode }, path, query, fn);
};

/**
 * Get a list of embed URLs available on a site.
 *
 * Note: The current user must have publishing access.
 *
 * @api public
 */

Site.prototype.embeds = function (query, fn) {
  var path = '/sites/' + this._id + '/embeds';
  return request.get(this.wpcom, null, path, query, fn);
};

/**
 * Get a rendered embed for a site.
 *
 * Note: The current user must have publishing access.
 *
 * @param {String} embed
 * @api public
 */

Site.prototype.renderEmbed = function (embed, query, fn) {
  if ('string' !== typeof embed) {
    throw new TypeError('expected an embed String');
  }

  var path = '/sites/' + this._id + '/embeds/render';
  return request.get(this.wpcom, { embed_url: embed }, path, query, fn);
};

/**
 * Expose `Site` module
 */

module.exports = Site;
<commit_msg>site: rename shortcodes() to shortcodesList(), and embeds() to embedsList()<commit_after>
/**
 * Module dependencies.
 */

var Post = require('./post');
var Category = require('./category');
var Tag = require('./tag');
var Media = require('./media');
var Comment = require('./comment');
var Follow = require('./follow');
var request = require('./util/request');
var debug = require('debug')('wpcom:site');

/**
 * Resources array
 * A list of endpoints with the same structure
 */

var resources = [
  'categories',
  'comments',
  'follows',
  'media',
  'posts',
  [ 'stats', 'stats' ],
  [ 'statsVisits', 'stats/visits' ],
  [ 'statsReferrers', 'stats/referrers' ],
  [ 'statsTopPosts', 'stats/top-posts' ],
  [ 'statsCountryViews', 'stats/country-views' ],
  [ 'statsClicks', 'stats/clicks' ],
  [ 'statsSearchTerms', 'stats/search-terms' ],
  'tags',
  'users'
];

/**
 * Create a Site instance
 *
 * @param {WPCOM} wpcom
 * @api public
 */

function Site(id, wpcom) {
  if (!(this instanceof Site)) {
    return new Site(id, wpcom);
  }

  this.wpcom = wpcom;

  debug('set %o site id', id);
  this._id = encodeURIComponent(id);
}

/**
 * Require site information
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Site.prototype.get = function (query, fn) {
  return request.get(this.wpcom, null, '/sites/' + this._id, query, fn);
};

/**
 * List method builder
 *
 * @param {String} subpath
 * @param {Function}
 * @api private
 */

function list (subpath, apiVersion) {

  /**
   * Return the <names>List method
   *
   * @param {Object} [query]
   * @param {Function} fn
   * @api public
   */

  return function (query, fn) {
    var path = '/sites/' + this._id + '/' + subpath;
    return request.get(this.wpcom, { apiVersion: apiVersion }, path, query, fn);
  };
}

// walk for each resource and create related method
var i, res, isarr, name, subpath, apiVersion;
for (i = 0; i < resources.length; i++) {
  res = resources[i];
  isarr = Array.isArray(res);

  name =  isarr ? res[0] : res + 'List';
  subpath = isarr ? res[1] : res;
  apiVersion = isarr && 'string' === typeof res[2] ? res[2] : '1';

  debug('adding %o method in %o sub-path (v%o)', 'site.' + name + '()', subpath, apiVersion);
  Site.prototype[name] = list(subpath, apiVersion);
}

/**
 * :POST:
 * Create a `Post` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.post = function (id) {
  return new Post(id, this._id, this.wpcom);
};

/**
 * :POST:
 * Add a new blog post
 *
 * @param {Object} body
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addPost = function (body, fn) {
  var post = new Post(null, this._id, this.wpcom);
  return post.add(body, fn);
};

/**
 * :POST:
 * Delete a blog post
 *
 * @param {String} id
 * @param {Function} fn
 * @return {Post} remove Post instance
 */

Site.prototype.deletePost = function (id, fn) {
  var post = new Post(id, this._id, this.wpcom);
  return post.delete(fn);
};

/**
 * Create a `Media` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.media = function (id) {
  return new Media(id, this._id, this.wpcom);
};

/**
 * Add a media from a file
 *
 * @param {Object} [query]
 * @param {Array|String} files
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addMediaFiles = function (query, files, fn) {
  var media = new Media(null, this._id, this.wpcom);
  return media.addFiles(query, files, fn);
};

/**
 * Add a new media from url
 *
 * @param {Object} [query]
 * @param {Array|String} files
 * @param {Function} fn
 * @return {Post} new Post instance
 */

Site.prototype.addMediaUrls = function (query, files, fn) {
  var media = new Media(null, this._id, this.wpcom);
  return media.addUrls(query, files, fn);
};

/**
 * Delete a blog media
 *
 * @param {String} id
 * @param {Function} fn
 * @return {Post} removed Media instance
 */

Site.prototype.deleteMedia = function (id, fn) {
  var media = new Media(id, this._id, this.wpcom);
  return media.del(fn);
};

/**
 * Create a `Comment` instance
 *
 * @param {String} id
 * @api public
 */

Site.prototype.comment = function (id) {
  return new Comment(id, null, this._id, this.wpcom);
};

/**
 * Create a `Follow` instance
 *
 * @api public
 */

Site.prototype.follow = function () {
  return new Follow(this._id, this.wpcom);
};

/**
 * Create a `Category` instance
 * Set `cat` alias
 *
 * @param {String} [slug]
 * @api public
 */

Site.prototype.cat = Site.prototype.category = function (slug) {
  return new Category(slug, this._id, this.wpcom);
};

/**
 * Create a `Tag` instance
 *
 * @param {String} [slug]
 * @api public
 */

Site.prototype.tag = function (slug) {
  return new Tag(slug, this._id, this.wpcom);
};

/**
 * Get a list of shortcodes available on a site.
 *
 * Note: The current user must have publishing access.
 *
 * @api public
 */

Site.prototype.shortcodesList = function (query, fn) {
  var path = '/sites/' + this._id + '/shortcodes';
  return request.get(this.wpcom, null, path, query, fn);
};

/**
 * Get a rendered shortcode for a site.
 *
 * Note: The current user must have publishing access.
 *
 * @param {String} shortcode
 * @api public
 */

Site.prototype.renderShortcode = function (shortcode, query, fn) {
  if ('string' !== typeof shortcode) {
    throw new TypeError('expected a shortcode String');
  }

  var path = '/sites/' + this._id + '/shortcodes/render';
  return request.get(this.wpcom, { shortcode: shortcode }, path, query, fn);
};

/**
 * Get a list of embed URLs available on a site.
 *
 * Note: The current user must have publishing access.
 *
 * @api public
 */

Site.prototype.embedsList = function (query, fn) {
  var path = '/sites/' + this._id + '/embeds';
  return request.get(this.wpcom, null, path, query, fn);
};

/**
 * Get a rendered embed for a site.
 *
 * Note: The current user must have publishing access.
 *
 * @param {String} embed
 * @api public
 */

Site.prototype.renderEmbed = function (embed, query, fn) {
  if ('string' !== typeof embed) {
    throw new TypeError('expected an embed String');
  }

  var path = '/sites/' + this._id + '/embeds/render';
  return request.get(this.wpcom, { embed_url: embed }, path, query, fn);
};

/**
 * Expose `Site` module
 */

module.exports = Site;
<|endoftext|>


<commit_before>/**
 * @author    Supercool Ltd <josh@supercooldesign.co.uk>
 * @copyright Copyright (c) 2014, Supercool Ltd
 * @see       http://supercooldesign.co.uk
 */

(function($){

/**
 * PimpMyMatrix Class
 */
Craft.PimpMyMatrix = Garnish.Base.extend(
{

  buttonConfig: null,
  reconstructSettingsTimeout: null,

  $matrixContainer: null,

  init: function(buttonConfig)
  {
    this.$matrixContainer = $('.matrix');

    this.buttonConfig = buttonConfig;

    this.addListener(Garnish.$win, 'load', 'loopMatrixFields');
  },

  loopMatrixFields: function()
  {

    var that = this;

    // loop each matrix field
    this.$matrixContainer.each($.proxy(function()
    {

      // sort block headings
      that.getFieldBlockTypes($(this));

      // sort buttons
      that.sortButtons($(this));

    }), this);

  },

  getFieldBlockTypes: function($matrixField)
  {
    // get matrix field handle out of DOM
    var matrixFieldHandle = this._getMatrixFieldName($matrixField);

    var that = this;

    // get array of blockTypes
    Craft.postActionRequest('pimpMyMatrix/getBlockTypesFromField', { matrixFieldHandle : matrixFieldHandle }, $.proxy(function(response, textStatus)
    {
      if (textStatus === 'success')
      {
        if (response.success)
        {

          // we have blockTypes so add them to the data object on the field
          $matrixField.data('blockTypes', response.blockTypes);

          // bind resize now on the matrix field
          that.addListener($matrixField, 'resize', 'addBlockHeadings');

          // ping addBlockHeadings anyway
          that.addBlockHeadings();

        }
      }
    }), this);

  },

  addBlockHeadings: function()
  {

    // loop available matrix fields
    this.$matrixContainer.each($.proxy(function()
    {

      // get field and blockTypes
      var $matrixField = $(this),
          blockTypes = $matrixField.data('blockTypes');

      // loop blocks if we have blockTypes
      if (typeof blockTypes !== "undefined") {
        $matrixField.find('.matrixblock').each($.proxy(function()
        {

          // cache block
          var $block = $(this);

          // final check that we haven't already added one in case something has gone mental
          if ( ! $block.hasClass('pimped') )
          {

            // get the block type handle
            var blockTypeHandle = $block.find('input[type="hidden"][name*="[type]"]').val();

            // using the blockTypes, match the handle to the blockType object
            var result = $.grep(blockTypes, function(e){ return e.handle === blockTypeHandle; });

            // check we have something
            if (result.length > 0)
            {

              // get the name and add it!
              var blockName = result[0].name;
              $block.addClass('pimped');
              $block.prepend('
'+blockName+'
')

            }

          }

        }), this);
      }

    }), this);

  },

  sortButtons: function($matrixField)
  {

    // get matrix field handle out of DOM
    var matrixFieldHandle = this._getMatrixFieldName($matrixField);

    // look for an object that matches this field in the config array
    var buttonConfig = $.grep(this.buttonConfig, function(e){ return e.fieldHandle === matrixFieldHandle; });

    // if we found one (and it has at least one group) execute our magic
    if ( buttonConfig[0] !== undefined )
    {

      // find and hide the original buttons
      var $origButtons = $matrixField.find('> .buttons');
      $origButtons.hide();

      // make our own container
      var $ourButtons = $('
').insertAfter($origButtons),
          $ourButtonsInner = $('
').appendTo($ourButtons);

      // loop each blockType / group pairing
      var buttonObject = buttonConfig[0].config;
      for (var key in buttonObject)
      {

        // check if group exists, add if not
        if ( $ourButtonsInner.find('[data-pimped-group="'+buttonObject[key]['group']+'"]').length === 0 )
        {
          $('
'+buttonObject[key]['group']+'

').appendTo($ourButtonsInner);
        }

        // find sub group
        $groupUl = $ourButtonsInner.find('[data-pimped-group="'+buttonObject[key]['group']+'"] ul');

        // make link in new sub group
        $('
'+buttonObject[key]['blockType']['name']+'
').appendTo($groupUl);

      }

      // make triggers MenuBtns
      $ourButtonsInner.find('.menubtn').each(function()
      {
        new Garnish.MenuBtn($(this),
        {
          onOptionSelect: function(option)
          {
            // find our type and click the correct original btn!
            var type = $(option).data('type');
            $origButtons.find('[data-type="'+type+'"]').click();
          }
        });
      });

    }

  },

  buttonConfigurator: function()
  {

    var that = this;

    // loop the fields we have on the page
    $('.pimpmymatrix-settings__list').each(function(){

      // get matrixFieldHandle
      var matrixFieldHandle = $(this).data('pimpmymatrix-field-handle');

      // check for a stored config
      var hasConfig = false;
      if ( that.buttonConfig !== undefined )
      {
        var buttonConfig = $.grep(that.buttonConfig, function(e){ return e.fieldHandle === matrixFieldHandle; });

        if ( buttonConfig[0] !== undefined )
        {
          hasConfig = true;
        }
      }

      // work out the groups - couldn’t we just get these from the php?
      if ( hasConfig )
      {
        var configObject = buttonConfig[0].config,
            groupArray = [];

        for (var key in configObject)
        {

          if ( ! Craft.inArray(configObject[key].group, groupArray) )
          {
            // save in array for later
            groupArray.push(configObject[key].group);
          }

        }
      }

      // loop this fields’ blockTypes
      $(this).children('li').each(function(){

        // add our group select box
        var blockTypeHandle = $(this).data('pimpmymatrix-blocktype-handle'),
            blockTypeName = $(this).data('pimpmymatrix-blocktype-name'),
            $field =$('
'+
              '
'+
                ''+blockTypeName+''+
              '
'+
              '

'+
            '
'),
            $select = $('<select id="pimpmymatrix-blocktypeselect-'+blockTypeHandle+'" name="pimpmymatrix-blocktypeselect-'+blockTypeHandle+'" />').appendTo($field.find('.select'));

        $('<option value="" selected></option>').appendTo($select);

        // add any existing options
        if ( hasConfig )
        {
          for (var key in groupArray) {
            $('<option value="'+groupArray[key]+'">'+groupArray[key]+'</option>').appendTo($select);
          }
        }

        // add field to this li
        $(this).append($field);

        if ( hasConfig )
        {
          // get config from current settings by blockType.handle
          var blockTypeConfig = $.grep(configObject, function(e){ return e.blockType.handle === blockTypeHandle; });

          // check its in the settings array
          if ( blockTypeConfig[0] !== undefined )
          {
            // set value of the select
            $(this).find('select').val(blockTypeConfig[0].group);
          }

        }

        // watch select changes
        that.addListener($(this), 'change', 'reconstructSettings');

      });

    });

  },

  bindTextchanges: function(settings)
  {

    // bind textchanges on textareas inside table
    var $textareas = $('#'+settings.tableId).find('tbody textarea');
    this.addListener($textareas, 'textchange', 'reconstructSelects');

  },

  reconstructSelects: function()
  {

    var that = this;

    // loop all fields
    $('.pimpmymatrix-settings__list').each(function(){

      // make new option list from table
      $selectTmlp = $('<select><option value=""></option></select>');
      $('#settings-pimpmymatrix-grouptable-'+$(this).data('pimpmymatrix-field-handle')).find('tbody textarea').each(function(){
        $('<option value="'+$(this).val()+'">'+$(this).val()+'</option>').appendTo($selectTmlp);
      });

      // loop each li
      $(this).find('li').each(function(){

        // replace select with options from our select template
        var $select = $(this).find('select'),
            selectVal = $select.val();

        // remove options
        $(this).find('option').remove();

        // clone in our new options
        $selectTmlp.find('option').clone().appendTo($select);

        // set the value of the select back
        $select.val(selectVal);

      });

    }).promise().done(function(){
      that.reconstructSettings();
    });

  },

  reconstructSettings: function()
  {

    if (this.reconstructSettingsTimeout)
    {
      clearTimeout(this.reconstructSettingsTimeout);
    }

    this.reconstructSettingsTimeout = setTimeout(function()
    {

      var settingsArray = [];

      // loop each field
      $('.pimpmymatrix-settings__list').each(function(){

        var hasSomeValue = false;

        // make settings object
        var settingsObject = {
          "fieldHandle" : $(this).data('pimpmymatrix-field-handle'),
          "config" : []
        };

        // loop li's
        $(this).find('li').each(function(){

          // check we have a value in the select
          var selectVal = $(this).find('select').val();
          if ( selectVal !== '' && selectVal !== null )
          {
            // set this to true so we know at least one of them was selected
            hasSomeValue = true;

            // add to config array in the object
            settingsObject.config.push(
              {
                "blockType" : {
                  "handle"  : $(this).data('pimpmymatrix-blocktype-handle'),
                  "name"    : $(this).data('pimpmymatrix-blocktype-name')
                },
                "group"     : selectVal
              }
            );
          }

        });

        // if there was at least one group value
        // push the whole object into the settingsArray
        if ( hasSomeValue )
        {
          settingsArray.push(settingsObject);
        }

      });

      // pop the settingsArray into our hidden field
      $('#settings-buttonConfig').val(JSON.stringify(settingsArray));

    }, 300);

  },

  /**
   * This simply returns a fieldHandle if it can get one or false if not
   */
  _getMatrixFieldName: function($matrixField)
  {
    var matrixFieldName = $matrixField.siblings('input[type="hidden"][name*="fields"]').prop('name'),
        regExp  = /fields\[([^\]]+)\]/,
        matches = regExp.exec(matrixFieldName),
        matrixFieldHandle = matches[1];

    if ( matrixFieldHandle != '' )
    {
      return matrixFieldHandle;
    }
    else
    {
      return false;
    }
  }

});

})(jQuery);
<commit_msg>Fix another undefined grep possibility<commit_after>/**
 * @author    Supercool Ltd <josh@supercooldesign.co.uk>
 * @copyright Copyright (c) 2014, Supercool Ltd
 * @see       http://supercooldesign.co.uk
 */

(function($){

/**
 * PimpMyMatrix Class
 */
Craft.PimpMyMatrix = Garnish.Base.extend(
{

  buttonConfig: null,
  reconstructSettingsTimeout: null,

  $matrixContainer: null,

  init: function(buttonConfig)
  {
    this.$matrixContainer = $('.matrix');

    this.buttonConfig = buttonConfig;

    this.addListener(Garnish.$win, 'load', 'loopMatrixFields');
  },

  loopMatrixFields: function()
  {

    var that = this;

    // loop each matrix field
    this.$matrixContainer.each($.proxy(function()
    {

      // sort block headings
      that.getFieldBlockTypes($(this));

      // sort buttons
      that.sortButtons($(this));

    }), this);

  },

  getFieldBlockTypes: function($matrixField)
  {
    // get matrix field handle out of DOM
    var matrixFieldHandle = this._getMatrixFieldName($matrixField);

    var that = this;

    // get array of blockTypes
    Craft.postActionRequest('pimpMyMatrix/getBlockTypesFromField', { matrixFieldHandle : matrixFieldHandle }, $.proxy(function(response, textStatus)
    {
      if (textStatus === 'success')
      {
        if (response.success)
        {

          // we have blockTypes so add them to the data object on the field
          $matrixField.data('blockTypes', response.blockTypes);

          // bind resize now on the matrix field
          that.addListener($matrixField, 'resize', 'addBlockHeadings');

          // ping addBlockHeadings anyway
          that.addBlockHeadings();

        }
      }
    }), this);

  },

  addBlockHeadings: function()
  {

    // loop available matrix fields
    this.$matrixContainer.each($.proxy(function()
    {

      // get field and blockTypes
      var $matrixField = $(this),
          blockTypes = $matrixField.data('blockTypes');

      // loop blocks if we have blockTypes
      if (typeof blockTypes !== "undefined") {
        $matrixField.find('.matrixblock').each($.proxy(function()
        {

          // cache block
          var $block = $(this);

          // final check that we haven't already added one in case something has gone mental
          if ( ! $block.hasClass('pimped') )
          {

            // get the block type handle
            var blockTypeHandle = $block.find('input[type="hidden"][name*="[type]"]').val();

            // using the blockTypes, match the handle to the blockType object
            var result = $.grep(blockTypes, function(e){ return e.handle === blockTypeHandle; });

            // check we have something
            if (result.length > 0)
            {

              // get the name and add it!
              var blockName = result[0].name;
              $block.addClass('pimped');
              $block.prepend('
'+blockName+'
')

            }

          }

        }), this);
      }

    }), this);

  },

  sortButtons: function($matrixField)
  {

    // get matrix field handle out of DOM
    var matrixFieldHandle = this._getMatrixFieldName($matrixField);

    // look for an object that matches this field in the config array
    if ( this.buttonConfig !== undefined )
    {
      var buttonConfig = $.grep(this.buttonConfig, function(e){ return e.fieldHandle === matrixFieldHandle; });

      // if we found one (and it has at least one group) execute our magic
      if ( buttonConfig[0] !== undefined )
      {

        // find and hide the original buttons
        var $origButtons = $matrixField.find('> .buttons');
        $origButtons.hide();

        // make our own container
        var $ourButtons = $('
').insertAfter($origButtons),
            $ourButtonsInner = $('
').appendTo($ourButtons);

        // loop each blockType / group pairing
        var buttonObject = buttonConfig[0].config;
        for (var key in buttonObject)
        {

          // check if group exists, add if not
          if ( $ourButtonsInner.find('[data-pimped-group="'+buttonObject[key]['group']+'"]').length === 0 )
          {
            $('
'+buttonObject[key]['group']+'

').appendTo($ourButtonsInner);
          }

          // find sub group
          $groupUl = $ourButtonsInner.find('[data-pimped-group="'+buttonObject[key]['group']+'"] ul');

          // make link in new sub group
          $('
'+buttonObject[key]['blockType']['name']+'
').appendTo($groupUl);

        }

        // make triggers MenuBtns
        $ourButtonsInner.find('.menubtn').each(function()
        {
          new Garnish.MenuBtn($(this),
          {
            onOptionSelect: function(option)
            {
              // find our type and click the correct original btn!
              var type = $(option).data('type');
              $origButtons.find('[data-type="'+type+'"]').click();
            }
          });
        });

      }
    }

  },

  buttonConfigurator: function()
  {

    var that = this;

    // loop the fields we have on the page
    $('.pimpmymatrix-settings__list').each(function(){

      // get matrixFieldHandle
      var matrixFieldHandle = $(this).data('pimpmymatrix-field-handle');

      // check for a stored config
      var hasConfig = false;
      if ( that.buttonConfig !== undefined )
      {
        var buttonConfig = $.grep(that.buttonConfig, function(e){ return e.fieldHandle === matrixFieldHandle; });

        if ( buttonConfig[0] !== undefined )
        {
          hasConfig = true;
        }
      }

      // work out the groups - couldn’t we just get these from the php?
      if ( hasConfig )
      {
        var configObject = buttonConfig[0].config,
            groupArray = [];

        for (var key in configObject)
        {

          if ( ! Craft.inArray(configObject[key].group, groupArray) )
          {
            // save in array for later
            groupArray.push(configObject[key].group);
          }

        }
      }

      // loop this fields’ blockTypes
      $(this).children('li').each(function(){

        // add our group select box
        var blockTypeHandle = $(this).data('pimpmymatrix-blocktype-handle'),
            blockTypeName = $(this).data('pimpmymatrix-blocktype-name'),
            $field =$('
'+
              '
'+
                ''+blockTypeName+''+
              '
'+
              '

'+
            '
'),
            $select = $('<select id="pimpmymatrix-blocktypeselect-'+blockTypeHandle+'" name="pimpmymatrix-blocktypeselect-'+blockTypeHandle+'" />').appendTo($field.find('.select'));

        $('<option value="" selected></option>').appendTo($select);

        // add any existing options
        if ( hasConfig )
        {
          for (var key in groupArray) {
            $('<option value="'+groupArray[key]+'">'+groupArray[key]+'</option>').appendTo($select);
          }
        }

        // add field to this li
        $(this).append($field);

        if ( hasConfig )
        {
          // get config from current settings by blockType.handle
          var blockTypeConfig = $.grep(configObject, function(e){ return e.blockType.handle === blockTypeHandle; });

          // check its in the settings array
          if ( blockTypeConfig[0] !== undefined )
          {
            // set value of the select
            $(this).find('select').val(blockTypeConfig[0].group);
          }

        }

        // watch select changes
        that.addListener($(this), 'change', 'reconstructSettings');

      });

    });

  },

  bindTextchanges: function(settings)
  {

    // bind textchanges on textareas inside table
    var $textareas = $('#'+settings.tableId).find('tbody textarea');
    this.addListener($textareas, 'textchange', 'reconstructSelects');

  },

  reconstructSelects: function()
  {

    var that = this;

    // loop all fields
    $('.pimpmymatrix-settings__list').each(function(){

      // make new option list from table
      $selectTmlp = $('<select><option value=""></option></select>');
      $('#settings-pimpmymatrix-grouptable-'+$(this).data('pimpmymatrix-field-handle')).find('tbody textarea').each(function(){
        $('<option value="'+$(this).val()+'">'+$(this).val()+'</option>').appendTo($selectTmlp);
      });

      // loop each li
      $(this).find('li').each(function(){

        // replace select with options from our select template
        var $select = $(this).find('select'),
            selectVal = $select.val();

        // remove options
        $(this).find('option').remove();

        // clone in our new options
        $selectTmlp.find('option').clone().appendTo($select);

        // set the value of the select back
        $select.val(selectVal);

      });

    }).promise().done(function(){
      that.reconstructSettings();
    });

  },

  reconstructSettings: function()
  {

    if (this.reconstructSettingsTimeout)
    {
      clearTimeout(this.reconstructSettingsTimeout);
    }

    this.reconstructSettingsTimeout = setTimeout(function()
    {

      var settingsArray = [];

      // loop each field
      $('.pimpmymatrix-settings__list').each(function(){

        var hasSomeValue = false;

        // make settings object
        var settingsObject = {
          "fieldHandle" : $(this).data('pimpmymatrix-field-handle'),
          "config" : []
        };

        // loop li's
        $(this).find('li').each(function(){

          // check we have a value in the select
          var selectVal = $(this).find('select').val();
          if ( selectVal !== '' && selectVal !== null )
          {
            // set this to true so we know at least one of them was selected
            hasSomeValue = true;

            // add to config array in the object
            settingsObject.config.push(
              {
                "blockType" : {
                  "handle"  : $(this).data('pimpmymatrix-blocktype-handle'),
                  "name"    : $(this).data('pimpmymatrix-blocktype-name')
                },
                "group"     : selectVal
              }
            );
          }

        });

        // if there was at least one group value
        // push the whole object into the settingsArray
        if ( hasSomeValue )
        {
          settingsArray.push(settingsObject);
        }

      });

      // pop the settingsArray into our hidden field
      $('#settings-buttonConfig').val(JSON.stringify(settingsArray));

    }, 300);

  },

  /**
   * This simply returns a fieldHandle if it can get one or false if not
   */
  _getMatrixFieldName: function($matrixField)
  {
    var matrixFieldName = $matrixField.siblings('input[type="hidden"][name*="fields"]').prop('name'),
        regExp  = /fields\[([^\]]+)\]/,
        matches = regExp.exec(matrixFieldName),
        matrixFieldHandle = matches[1];

    if ( matrixFieldHandle != '' )
    {
      return matrixFieldHandle;
    }
    else
    {
      return false;
    }
  }

});

})(jQuery);
<|endoftext|>


<commit_before>var _ = require('lazy.js');
var blessed = require('blessed');
var fs = require('fs');
var path = require('path');

var Editor = require('./editor');
var Coordinate = require('./coordinate');
var markup = require('./markup');
var util = require('./util');
var textUtil = require('./textUtil');

function Slap (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) { return new Slap(opts); }

  blessed.Screen.call(self, opts);

  self.header = new blessed.Box(_({
    tags: true,
    top: 0,
    left: 0,
    right: 0,
    height: 1
  }).merge(opts.header || {}).merge({parent: self}).toObject());

  self.editor = new Editor(_({
    parent: self,
    top: 1,
    left: 0,
    right: 0,
    bottom: 0
  }).merge(opts.editor || {}).toObject());
  self.editor.focus();

  self._initHandlers().render();
}
Slap.prototype.__proto__ = blessed.Screen.prototype;

Slap.normalizePath = function (givenPath) {
  if (!givenPath) { givenPath = ''; }
  if (givenPath[0] === '~') { givenPath = path.join(process.env.HOME, givenPath.slice(1)); }
  return path.normalize(givenPath);
};
Slap.prototype.path = util.getterSetter('path', null, Slap.normalizePath);
Slap.prototype.open = function (givenPath) {
  var self = this;

  givenPath = Slap.normalizePath(givenPath);
  fs.readFile(givenPath, function (err, data) {
    if (err) { throw err; }
    self.path(givenPath);

    self.editor.text(data, givenPath.split('.').pop());
  });

  return self;
};
Slap.prototype.save = function (givenPath) {
  var self = this;
  givenPath = givenPath ? Slap.normalizePath(givenPath) : self.path();
  if (!givenPath) { return; }

  var text = self.editor.text();
  fs.writeFile(givenPath, text, {flags: 'w'}, function (err) {
    if (!err) {
      self
        .path(givenPath)
        .message("saved to " + givenPath, 'success');
      self.editor.changeStack.save();
      self.emit('save', givenPath, text);
    } else {
      switch (err.code) {
        case 'EACCES': case 'EISDIR':
          self.message(err.message, 'error');
          break;
        default: throw err;
      }
    }
  });
};
Slap.prototype.saveAs = function () {
  var self = this;
  self.prompt("save as", function (results) {
    if (results) {
      self.save(results[0]);
    }
  });
};

Slap.prototype.prompt = function (prompts, cb) {
  var self = this;

  prompts = typeof prompts !== 'string' ? prompts : [prompts];

  self.closePrompt();
  self.editor.bottom += prompts.length;
  self.form = new blessed.Form({
    parent: self,
    height: prompts.length,
    left: 0,
    right: 0,
    bottom: 0
  });

  var labelWidth = _(prompts).pluck('length').max() + 3;
  var label = new blessed.Box({
    parent: self.form,
    tags: true,
    content: prompts.map(function (prompt) {
      return markup.markupLine(
        (' ' + prompt + ':' + _.repeat(' ', labelWidth).join('')).slice(0, labelWidth),
      self.options.style.prompt || self.options.style.main);
    }).join('\n'),
    top: 0,
    left: 0,
    width: labelWidth,
    bottom: 0
  });

  function done (submit) {
    self.closePrompt(true);
    cb(submit ? _(inputs).pluck('value').toArray() : null);

    // FIXME: nasty hack to stop Enter key event propagating to the wrong element
    self.lockKeys = true;
    process.nextTick(function () { self.lockKeys = false; });
  }

  var inputs = prompts.map(function (prompt, i) {
    var input = new blessed.Textbox(_(self.options.editor || {}).merge({
      parent: self.form,
      inputOnFocus: true,
      top: i,
      height: 1,
      left: labelWidth,
      right: 0
    }).merge(self.options.input || {}).toObject());
    if (!i) { input.focus(); }
    input.on('submit', done.bind(null, true));
    input.on('cancel', done.bind(null, false));
    return input;
  });

  self.render();
  return self;
};

Slap.prototype.closePrompt = function (focus) {
  var self = this;

  if (self.form) {
    self.remove(self.form);
    self.form = null;
    if (focus) { self.editor.focus(); }
    self.editor.bottom = 0;
    self.render();
  }

  return self;
};

Slap.prototype.exit = function () {
  process.exit(0);

  return this; // Just in case
};

Slap.prototype.message = util.getterSetter('message', null, function (message, styleName) {
  var self = this;

  if (message !== null) {
    setTimeout(function () {
      self.message(null);
    }, self.options.messageDuration);
  }

  return message !== null ? markup.markupLine(message, self.options.style[styleName]) : null;
});

Slap._regExpRegExp = /^\/(.+)\/$/;
Slap.prototype._initHandlers = function () {
  var self = this;

  var style = self.options.style;
  self.on('element keypress', function (el, ch, key) {
    switch (util.getBinding(self.options.bindings, key)) {
      case 'quit':
        if (self.editor.changeStack.dirty()) {
          self.exit(); // FIXME: warn user focused element needs finishing
        } else {
          self.exit();
        }
        break;
      case 'find':
        self.prompt("find (/.*/ for regex)", function (results) {
          if (results) {
            pattern = results[0];
            var regExpMatch = pattern.match(Slap._regExpRegExp);
            pattern = regExpMatch
              ? new RegExp(regExpMatch[1])
              : new RegExp(textUtil.escapeRegExp(pattern));

            var startPos = self.editor.cursor();
            startPos.x += 1;
            var pos = textUtil.find(self.editor.lines(), pattern, startPos);

            if (pos) {
              self.editor.select(null, pos);
            } else {
              pos = textUtil.find(self.editor.lines(), pattern);
              if (pos) {
                if (Coordinate.linear.cmp(pos, self.editor.cursor()) === 0) {
                  self.message("this is the only occurrence", 'info');
                }
                self.editor.select(null, pos);
              } else {
                self.message("no matches", 'warning');
              }
            }
          }
        });
        break;
      case 'save':
        if (self.path()) { self.save(); }
        else { self.saveAs(); }
        break;
      case 'saveAs':
        self.saveAs();
        break;
    }
  });

  ['resize', 'message', 'save'].forEach(function (evt) {
    self.on(evt, function () { self.render(); });
  });

  ['change', 'cursor', 'insertMode'].forEach(function (evt) {
    self.editor.on(evt, function () { self.render(); });
  });

  return self;
};
Slap.prototype.render = function () {
  var self = this;

  var style = self.options.style;
  var cursor = self.editor.cursor();

  var left = ' \u270b ';
  var originalPath = self.path();
  var markupPath = markup.escapeCurlies(originalPath || 'new file');
  if (self.editor.changeStack.dirty() || !originalPath) {
    markupPath = markup.markupLine(markupPath+'*', style.changed);
  }
  left += markupPath;

  var right = [(cursor.y + 1)+','+(cursor.x + 1) + ' ('+self.editor.lines().length+')'];
  if (!self.editor.insertMode()) { right.unshift(markup.markupLine('ovr', '{red-bg}{white-fg}')); }
  var message = self.message();
  if (message) { right.unshift(message); }
  right = right.join('  ') + ' ';

  var remainingWidth = self.header.width - self.header.iwidth - markup.removeMarkup(left + right).length;
  self.header.setContent(markup.markupLine(
    left + _.repeat(' ', remainingWidth).join('') + right
  , style.header || style.main));

  return blessed.Screen.prototype.render.apply(self, arguments);
};

module.exports = Slap;
<commit_msg>Adds "search wrapped" message<commit_after>var _ = require('lazy.js');
var blessed = require('blessed');
var fs = require('fs');
var path = require('path');

var Editor = require('./editor');
var Coordinate = require('./coordinate');
var markup = require('./markup');
var util = require('./util');
var textUtil = require('./textUtil');

function Slap (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) { return new Slap(opts); }

  blessed.Screen.call(self, opts);

  self.header = new blessed.Box(_({
    tags: true,
    top: 0,
    left: 0,
    right: 0,
    height: 1
  }).merge(opts.header || {}).merge({parent: self}).toObject());

  self.editor = new Editor(_({
    parent: self,
    top: 1,
    left: 0,
    right: 0,
    bottom: 0
  }).merge(opts.editor || {}).toObject());
  self.editor.focus();

  self._initHandlers().render();
}
Slap.prototype.__proto__ = blessed.Screen.prototype;

Slap.normalizePath = function (givenPath) {
  if (!givenPath) { givenPath = ''; }
  if (givenPath[0] === '~') { givenPath = path.join(process.env.HOME, givenPath.slice(1)); }
  return path.normalize(givenPath);
};
Slap.prototype.path = util.getterSetter('path', null, Slap.normalizePath);
Slap.prototype.open = function (givenPath) {
  var self = this;

  givenPath = Slap.normalizePath(givenPath);
  fs.readFile(givenPath, function (err, data) {
    if (err) { throw err; }
    self.path(givenPath);

    self.editor.text(data, givenPath.split('.').pop());
  });

  return self;
};
Slap.prototype.save = function (givenPath) {
  var self = this;
  givenPath = givenPath ? Slap.normalizePath(givenPath) : self.path();
  if (!givenPath) { return; }

  var text = self.editor.text();
  fs.writeFile(givenPath, text, {flags: 'w'}, function (err) {
    if (!err) {
      self
        .path(givenPath)
        .message("saved to " + givenPath, 'success');
      self.editor.changeStack.save();
      self.emit('save', givenPath, text);
    } else {
      switch (err.code) {
        case 'EACCES': case 'EISDIR':
          self.message(err.message, 'error');
          break;
        default: throw err;
      }
    }
  });
};
Slap.prototype.saveAs = function () {
  var self = this;
  self.prompt("save as", function (results) {
    if (results) {
      self.save(results[0]);
    }
  });
};

Slap.prototype.prompt = function (prompts, cb) {
  var self = this;

  prompts = typeof prompts !== 'string' ? prompts : [prompts];

  self.closePrompt();
  self.editor.bottom += prompts.length;
  self.form = new blessed.Form({
    parent: self,
    height: prompts.length,
    left: 0,
    right: 0,
    bottom: 0
  });

  var labelWidth = _(prompts).pluck('length').max() + 3;
  var label = new blessed.Box({
    parent: self.form,
    tags: true,
    content: prompts.map(function (prompt) {
      return markup.markupLine(
        (' ' + prompt + ':' + _.repeat(' ', labelWidth).join('')).slice(0, labelWidth),
      self.options.style.prompt || self.options.style.main);
    }).join('\n'),
    top: 0,
    left: 0,
    width: labelWidth,
    bottom: 0
  });

  function done (submit) {
    self.closePrompt(true);
    cb(submit ? _(inputs).pluck('value').toArray() : null);

    // FIXME: nasty hack to stop Enter key event propagating to the wrong element
    self.lockKeys = true;
    process.nextTick(function () { self.lockKeys = false; });
  }

  var inputs = prompts.map(function (prompt, i) {
    var input = new blessed.Textbox(_(self.options.editor || {}).merge({
      parent: self.form,
      inputOnFocus: true,
      top: i,
      height: 1,
      left: labelWidth,
      right: 0
    }).merge(self.options.input || {}).toObject());
    if (!i) { input.focus(); }
    input.on('submit', done.bind(null, true));
    input.on('cancel', done.bind(null, false));
    return input;
  });

  self.render();
  return self;
};

Slap.prototype.closePrompt = function (focus) {
  var self = this;

  if (self.form) {
    self.remove(self.form);
    self.form = null;
    if (focus) { self.editor.focus(); }
    self.editor.bottom = 0;
    self.render();
  }

  return self;
};

Slap.prototype.exit = function () {
  process.exit(0);

  return this; // Just in case
};

Slap.prototype.message = util.getterSetter('message', null, function (message, styleName) {
  var self = this;

  if (message !== null) {
    setTimeout(function () {
      self.message(null);
    }, self.options.messageDuration);
  }

  return message !== null ? markup.markupLine(message, self.options.style[styleName]) : null;
});

Slap._regExpRegExp = /^\/(.+)\/$/;
Slap.prototype._initHandlers = function () {
  var self = this;

  var style = self.options.style;
  self.on('element keypress', function (el, ch, key) {
    switch (util.getBinding(self.options.bindings, key)) {
      case 'quit':
        if (self.editor.changeStack.dirty()) {
          self.exit(); // FIXME: warn user focused element needs finishing
        } else {
          self.exit();
        }
        break;
      case 'find':
        self.prompt("find (/.*/ for regex)", function (results) {
          if (results) {
            pattern = results[0];
            var regExpMatch = pattern.match(Slap._regExpRegExp);
            pattern = regExpMatch
              ? new RegExp(regExpMatch[1])
              : new RegExp(textUtil.escapeRegExp(pattern));

            var startPos = self.editor.cursor();
            startPos.x += 1;
            var pos = textUtil.find(self.editor.lines(), pattern, startPos);

            if (pos) {
              self.editor.select(null, pos);
            } else {
              pos = textUtil.find(self.editor.lines(), pattern);
              if (pos) {
                self.message(Coordinate.linear.cmp(pos, self.editor.cursor()) !== 0
                  ? "search wrapped"
                  : "this is the only occurrence", 'info');
                self.editor.select(null, pos);
              } else {
                self.message("no matches", 'warning');
              }
            }
          }
        });
        break;
      case 'save':
        if (self.path()) { self.save(); }
        else { self.saveAs(); }
        break;
      case 'saveAs':
        self.saveAs();
        break;
    }
  });

  ['resize', 'message', 'save'].forEach(function (evt) {
    self.on(evt, function () { self.render(); });
  });

  ['change', 'cursor', 'insertMode'].forEach(function (evt) {
    self.editor.on(evt, function () { self.render(); });
  });

  return self;
};
Slap.prototype.render = function () {
  var self = this;

  var style = self.options.style;
  var cursor = self.editor.cursor();

  var left = ' \u270b ';
  var originalPath = self.path();
  var markupPath = markup.escapeCurlies(originalPath || 'new file');
  if (self.editor.changeStack.dirty() || !originalPath) {
    markupPath = markup.markupLine(markupPath+'*', style.changed);
  }
  left += markupPath;

  var right = [(cursor.y + 1)+','+(cursor.x + 1) + ' ('+self.editor.lines().length+')'];
  if (!self.editor.insertMode()) { right.unshift(markup.markupLine('ovr', '{red-bg}{white-fg}')); }
  var message = self.message();
  if (message) { right.unshift(message); }
  right = right.join('  ') + ' ';

  var remainingWidth = self.header.width - self.header.iwidth - markup.removeMarkup(left + right).length;
  self.header.setContent(markup.markupLine(
    left + _.repeat(' ', remainingWidth).join('') + right
  , style.header || style.main));

  return blessed.Screen.prototype.render.apply(self, arguments);
};

module.exports = Slap;
<|endoftext|>


<commit_before>// Sync
// ---------------
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const supportsReturning = (client) => _.includes(['postgresql', 'postgres', 'pg', 'oracle', 'mssql'], client);
const validLocks = ['forShare', 'forUpdate'];

// Sync is the dispatcher for any database queries,
// taking the "syncing" `model` or `collection` being queried, along with
// a hash of options that are used in the various query methods.
// If the `transacting` option is set, the query is assumed to be
// part of a transaction, and this information is passed along to `Knex`.
const Sync = function(syncing, options) {
  options = options || {};
  this.query = syncing.query();
  this.syncing = syncing.resetQuery();
  this.options = options;
  if (options.debug) this.query.debug();
  if (options.transacting) {
    this.query.transacting(options.transacting);
    if (validLocks.indexOf(options.lock) > -1) this.query[options.lock]();
  }
  if (options.withSchema) this.query.withSchema(options.withSchema);
};

_.extend(Sync.prototype, {
  // Prefix all keys of the passed in object with the
  // current table name
  prefixFields: function(fields) {
    const tableName = this.syncing.tableName;
    const prefixed = {};
    for (const key in fields) {
      prefixed[tableName + '.' + key] = fields[key];
    }
    return prefixed;
  },

  // Select the first item from the database - only used by models.
  first: Promise.method(function(attributes) {
    const model = this.syncing;
    const query = this.query;

    // We'll never use an JSON object for a search, because even
    // PostgreSQL, which has JSON type columns, does not support the `=`
    // operator.
    //
    // NOTE: `_.omit` returns an empty object, even if attributes are null.
    const whereAttributes = _.omitBy(attributes, (attribute, name) => {
      return _.isPlainObject(attribute) || name === model.idAttribute;
    });
    const formattedAttributes = model.format(whereAttributes);

    if (model.idAttribute in attributes) {
      formattedAttributes[model.idAttribute] = attributes[model.idAttribute];
    }

    if (!_.isEmpty(formattedAttributes)) query.where(this.prefixFields(formattedAttributes));
    query.limit(1);

    return this.select();
  }),

  // Runs a `count` query on the database, adding any necessary relational
  // constraints. Returns a promise that resolves to an integer count.
  count: Promise.method(function(column) {
    const knex = this.query,
      options = this.options,
      relatedData = this.syncing.relatedData,
      fks = {};

    return Promise.bind(this)
      .then(function() {
        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);
              relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
            } else if (relatedData.type === 'hasMany') {
              const fk = relatedData.key('foreignKey');
              knex.where(fk, relatedData.parentFk);
            }
          });
      })
      .then(function() {
        options.query = knex;

        /**
         * Counting event.
         *
         * Fired before a `count` query. A promise may be
         * returned from the event handler for async behaviour.
         *
         * @event Model#counting
         * @tutorial events
         * @param {Model}  model    The model firing the event.
         * @param {Object} options  Options object passed to {@link Model#count count}.
         * @returns {Promise}
         */
        return this.syncing.triggerThen('counting', this.syncing, options);
      })
      .then(function() {
        return knex.count((column || '*') + ' as count');
      })
      .then(function(rows) {
        return rows[0].count;
      });
  }),

  // Runs a `select` query on the database, adding any necessary relational
  // constraints, resetting the query when complete. If there are results and
  // eager loaded relations, those are fetched and returned on the model before
  // the promise is resolved. Any `success` handler passed in the
  // options will be called - used by both models & collections.
  select: Promise.method(function() {
    const knex = this.query;
    const options = this.options;
    const relatedData = this.syncing.relatedData;
    const fks = {};
    let columns = null;

    // Check if any `select` style statements have been called with column
    // specifications. This could include `distinct()` with no arguments, which
    // does not affect inform the columns returned.
    const queryContainsColumns = _(knex._statements)
      .filter({grouping: 'columns'})
      .some('value.length');

    return Promise.bind(this)
      .then(function() {
        // Set the query builder on the options, in-case we need to
        // access in the `fetching` event handlers.
        options.query = knex;

        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);

              return through.triggerThen('fetching', through, relatedData.pivotColumns, options).then(function() {
                relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
              });
            }
          });
      })
      .tap(() => {
        // If this is a relation, apply the appropriate constraints.
        if (relatedData) {
          relatedData.selectConstraints(knex, options);
        } else {
          // Call the function, if one exists, to constrain the eager loaded query.
          if (options._beforeFn) options._beforeFn.call(knex, knex);

          if (options.columns) {
            // Normalize single column name into array.
            columns = Array.isArray(options.columns) ? options.columns : [options.columns];
          } else if (!queryContainsColumns) {
            // If columns have already been selected via the `query` method
            // we will use them. Otherwise, select all columns in this table.
            columns = [_.result(this.syncing, 'tableName') + '.*'];
          }
        }

        // Set the query builder on the options, for access in the `fetching`
        // event handlers.
        options.query = knex;

        /**
         * Fired before a `fetch` operation. A promise may be returned from the event handler for
         * async behaviour.
         *
         * @example
         * const MyModel = bookshelf.model('MyModel', {
         *   initialize() {
         *     this.on('fetching', function(model, columns, options) {
         *       options.query.where('status', 'active')
         *     })
         *   }
         * })
         *
         * @event Model#fetching
         * @tutorial events
         * @param {Model} model The model which is about to be fetched.
         * @param {string[]} columns The columns to be retrieved by the query.
         * @param {Object} options Options object passed to {@link Model#fetch fetch}.
         * @param {QueryBuilder} options.query
         *   Query builder to be used for fetching. This can be used to modify or add to the query
         *   before it is executed. See example above.
         * @return {Promise}
         */
        return this.syncing.triggerThen('fetching', this.syncing, columns, options);
      })
      .then(() => knex.select(columns));
  }),

  // Issues an `insert` command on the query - only used by models.
  insert: Promise.method(function() {
    const syncing = this.syncing;
    return this.query.insert(
      syncing.format(_.extend(Object.create(null), syncing.attributes)),
      supportsReturning(this.query.client.config.client) ? syncing.idAttribute : null
    );
  }),

  // Issues an `update` command on the query - only used by models.
  update: Promise.method(function(attrs) {
    const syncing = this.syncing,
      query = this.query;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be updated without a "where" clause or an idAttribute.');
    }
    var updating = syncing.format(_.extend(Object.create(null), attrs));
    if (syncing.id === updating[syncing.idAttribute]) {
      delete updating[syncing.idAttribute];
    }
    return query.update(updating);
  }),

  // Issues a `delete` command on the query.
  del: Promise.method(function() {
    const query = this.query,
      syncing = this.syncing;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be destroyed without a "where" clause or an idAttribute.');
    }
    return this.query.del();
  })
});

module.exports = Sync;
<commit_msg>Return changes if database supports RETURNING<commit_after>// Sync
// ---------------
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const validLocks = ['forShare', 'forUpdate'];

function supportsReturning(client = {}) {
  if (!client.config || !client.config.client) return false;
  return ['postgresql', 'postgres', 'pg', 'oracle', 'mssql'].includes(client.config.client);
}

// Sync is the dispatcher for any database queries,
// taking the "syncing" `model` or `collection` being queried, along with
// a hash of options that are used in the various query methods.
// If the `transacting` option is set, the query is assumed to be
// part of a transaction, and this information is passed along to `Knex`.
const Sync = function(syncing, options) {
  options = options || {};
  this.query = syncing.query();
  this.syncing = syncing.resetQuery();
  this.options = options;
  if (options.debug) this.query.debug();
  if (options.transacting) {
    this.query.transacting(options.transacting);
    if (validLocks.indexOf(options.lock) > -1) this.query[options.lock]();
  }
  if (options.withSchema) this.query.withSchema(options.withSchema);
};

_.extend(Sync.prototype, {
  // Prefix all keys of the passed in object with the
  // current table name
  prefixFields: function(fields) {
    const tableName = this.syncing.tableName;
    const prefixed = {};
    for (const key in fields) {
      prefixed[tableName + '.' + key] = fields[key];
    }
    return prefixed;
  },

  // Select the first item from the database - only used by models.
  first: Promise.method(function(attributes) {
    const model = this.syncing;
    const query = this.query;

    // We'll never use an JSON object for a search, because even
    // PostgreSQL, which has JSON type columns, does not support the `=`
    // operator.
    //
    // NOTE: `_.omit` returns an empty object, even if attributes are null.
    const whereAttributes = _.omitBy(attributes, (attribute, name) => {
      return _.isPlainObject(attribute) || name === model.idAttribute;
    });
    const formattedAttributes = model.format(whereAttributes);

    if (model.idAttribute in attributes) {
      formattedAttributes[model.idAttribute] = attributes[model.idAttribute];
    }

    if (!_.isEmpty(formattedAttributes)) query.where(this.prefixFields(formattedAttributes));
    query.limit(1);

    return this.select();
  }),

  // Runs a `count` query on the database, adding any necessary relational
  // constraints. Returns a promise that resolves to an integer count.
  count: Promise.method(function(column) {
    const knex = this.query,
      options = this.options,
      relatedData = this.syncing.relatedData,
      fks = {};

    return Promise.bind(this)
      .then(function() {
        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);
              relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
            } else if (relatedData.type === 'hasMany') {
              const fk = relatedData.key('foreignKey');
              knex.where(fk, relatedData.parentFk);
            }
          });
      })
      .then(function() {
        options.query = knex;

        /**
         * Counting event.
         *
         * Fired before a `count` query. A promise may be
         * returned from the event handler for async behaviour.
         *
         * @event Model#counting
         * @tutorial events
         * @param {Model}  model    The model firing the event.
         * @param {Object} options  Options object passed to {@link Model#count count}.
         * @returns {Promise}
         */
        return this.syncing.triggerThen('counting', this.syncing, options);
      })
      .then(function() {
        return knex.count((column || '*') + ' as count');
      })
      .then(function(rows) {
        return rows[0].count;
      });
  }),

  // Runs a `select` query on the database, adding any necessary relational
  // constraints, resetting the query when complete. If there are results and
  // eager loaded relations, those are fetched and returned on the model before
  // the promise is resolved. Any `success` handler passed in the
  // options will be called - used by both models & collections.
  select: Promise.method(function() {
    const knex = this.query;
    const options = this.options;
    const relatedData = this.syncing.relatedData;
    const fks = {};
    let columns = null;

    // Check if any `select` style statements have been called with column
    // specifications. This could include `distinct()` with no arguments, which
    // does not affect inform the columns returned.
    const queryContainsColumns = _(knex._statements)
      .filter({grouping: 'columns'})
      .some('value.length');

    return Promise.bind(this)
      .then(function() {
        // Set the query builder on the options, in-case we need to
        // access in the `fetching` event handlers.
        options.query = knex;

        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);

              return through.triggerThen('fetching', through, relatedData.pivotColumns, options).then(function() {
                relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
              });
            }
          });
      })
      .tap(() => {
        // If this is a relation, apply the appropriate constraints.
        if (relatedData) {
          relatedData.selectConstraints(knex, options);
        } else {
          // Call the function, if one exists, to constrain the eager loaded query.
          if (options._beforeFn) options._beforeFn.call(knex, knex);

          if (options.columns) {
            // Normalize single column name into array.
            columns = Array.isArray(options.columns) ? options.columns : [options.columns];
          } else if (!queryContainsColumns) {
            // If columns have already been selected via the `query` method
            // we will use them. Otherwise, select all columns in this table.
            columns = [_.result(this.syncing, 'tableName') + '.*'];
          }
        }

        // Set the query builder on the options, for access in the `fetching`
        // event handlers.
        options.query = knex;

        /**
         * Fired before a `fetch` operation. A promise may be returned from the event handler for
         * async behaviour.
         *
         * @example
         * const MyModel = bookshelf.model('MyModel', {
         *   initialize() {
         *     this.on('fetching', function(model, columns, options) {
         *       options.query.where('status', 'active')
         *     })
         *   }
         * })
         *
         * @event Model#fetching
         * @tutorial events
         * @param {Model} model The model which is about to be fetched.
         * @param {string[]} columns The columns to be retrieved by the query.
         * @param {Object} options Options object passed to {@link Model#fetch fetch}.
         * @param {QueryBuilder} options.query
         *   Query builder to be used for fetching. This can be used to modify or add to the query
         *   before it is executed. See example above.
         * @return {Promise}
         */
        return this.syncing.triggerThen('fetching', this.syncing, columns, options);
      })
      .then(() => knex.select(columns));
  }),

  // Issues an `insert` command on the query - only used by models.
  insert: Promise.method(function() {
    const syncing = this.syncing;
    return this.query.insert(
      syncing.format(_.extend(Object.create(null), syncing.attributes)),
      supportsReturning(this.query.client) ? '*' : null
    );
  }),

  // Issues an `update` command on the query - only used by models.
  update: Promise.method(function(attrs) {
    const syncing = this.syncing,
      query = this.query;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be updated without a "where" clause or an idAttribute.');
    }
    var updating = syncing.format(_.extend(Object.create(null), attrs));
    if (syncing.id === updating[syncing.idAttribute]) {
      delete updating[syncing.idAttribute];
    }
    if (supportsReturning(query.client)) query.returning('*');
    return query.update(updating);
  }),

  // Issues a `delete` command on the query.
  del: Promise.method(function() {
    const query = this.query,
      syncing = this.syncing;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be destroyed without a "where" clause or an idAttribute.');
    }
    return this.query.del();
  })
});

module.exports = Sync;
<|endoftext|>


<commit_before>const helpers = require('./helpers');

module.exports = {
  /**
   * Fetch a list of all tags
   *
   * @param {callback} callback - An optional callback
   * @returns {promise}
   */
  listAllTags(callback) {
    return this.get(`${this.accountId}/tags`, {}, callback);
  },
  /**
   * Tag a subscriber
   *
   * @param {callback} callback - An optional callback
   * @param {object} payload - An object with the subscriber's email and tag
   * @returns {promise}
   */
  tagSubscriber(payload, callback) {
    return this.post(`${this.accountId}/tags`, { payload: [payload] }, callback);
  },
  /**
   * Remove a tag from a subscriber
   *
   * @param {string} email - The subscriber's email
   * @param {string} tag - The tag to be removed
   * @param {callback} callback - An optional callback
   * @returns {promise}
   */
  removeSubscriberTag(email, tag, callback) {
    const encodedEmail = helpers.escapeString(email);
    const encodedTag = helpers.escapeString(tag);
    return this.del(`${this.accountId}/subscribers/${encodedEmail}/tags/${encodedTag}`, {}, callback);
  }
};
<commit_msg>fix tagSubscriber payload<commit_after>const helpers = require('./helpers');

module.exports = {
  /**
   * Fetch a list of all tags
   *
   * @param {callback} callback - An optional callback
   * @returns {promise}
   */
  listAllTags(callback) {
    return this.get(`${this.accountId}/tags`, {}, callback);
  },
  /**
   * Tag a subscriber
   *
   * @param {callback} callback - An optional callback
   * @param {object} payload - An object with the subscriber's email and tag
   * @returns {promise}
   */
  tagSubscriber(payload, callback) {
    return this.post(`${this.accountId}/tags`, { payload: payload }, callback);
  },
  /**
   * Remove a tag from a subscriber
   *
   * @param {string} email - The subscriber's email
   * @param {string} tag - The tag to be removed
   * @param {callback} callback - An optional callback
   * @returns {promise}
   */
  removeSubscriberTag(email, tag, callback) {
    const encodedEmail = helpers.escapeString(email);
    const encodedTag = helpers.escapeString(tag);
    return this.del(`${this.accountId}/subscribers/${encodedEmail}/tags/${encodedTag}`, {}, callback);
  }
};
<|endoftext|>