<commit_before>'use strict';

var q = require('q');

var TaskModel = require('../models/task.js');
var ProjectModel = require('../models/project.js');
var DiscussionModel = require('../models/discussion.js');
var OfficeDocumentsModel = require('../models/document.js');

var _ = require('lodash') ;
var entityNameMap = {
  'task': {
    mainModel: TaskModel,
//      archiveModel: TaskArchiveModel,
    name: 'task'
  },
  'project': {
    mainModel: ProjectModel,
//      archiveModel: ProjectArchiveModel,
    name: 'project'
  },
  'discussion': {
    mainModel: DiscussionModel,
//      archiveModel: DiscussionArchiveModel,
    name: 'discussion'
  },
  'officeDocuments':{
    mainModel: OfficeDocumentsModel,
//      archiveModel: OfficeDocumentsArchiveModel,
    name: 'officeDocument'
  },
  'officeDocument': {
    mainModel: OfficeDocumentsModel,
//      archiveModel: OfficeDocumentsArchiveModel,
    name: 'officeDocument'
  },
};

exports.permError =
{
  denied: "permission denied",
  granted: "permission granted",
  system: "update problem in occured",
  db: "db issue occured",
  allowUpdateWatcher: "update permissions for user",
  allowAssign: "assignee update",
  allowUpdateContent: "content update",
  allowCreateContent: "content create"
} ;

/*
    function: search if value exist in array in the format of arraItem.id
    return: the index in which the value exist, or null otherwise.
*/
exports.searchIdIndex = function(idVal, arr) {
  for (let i = 0; i < arr.length; i++) {
    if (String(arr[i].id) === String(idVal)) {
      //  console.log("user EXISTS!!!");
      return i;
    }
  }
  return null;
};

/*
    function: return the values that exist in both input arrays,
    and the values that exist only in the first array.
    input: 2 arrays.
    return: the index in which the value exist, or null otherwise.
*/
function leftIntersect(arr1, arr2) {
  let filtered = arr1.filter(function(i) {
    return arr2.indexOf(i) == -1 ? true : false;
  });
  return filtered;
}

/*
    function: check if there is an id in arr2, that is not present in 1.
    input: 2 arrays.
    return: true if id is found in arr2 and arr1, false otherwise.
*/
function isDifferentArray(arr1, arr2) {
  // console.log(JSON.stringify(arr1)) ;
  // console.log(JSON.stringify(arr2)) ;
  let result = [] ;

  if(arr1.length != arr2.length) {
    return true ;
  }

  arr1.forEach(function(a1) {
    if(arr2[a1]) {
      result++ ;
    }
  })

  arr2.forEach(function(a2) {
    if(arr1[a2]) {
      result++ ;
    }
  })

  if (result == 0) {
    return false ;
  }

  return true;
}

/*
  update permission array
  input: old Document, new Document
  output: permissions array
*/
exports.updateContent = function(user, oldDoc, newDoc) {

  console.log("updateContent" ) ;

  if(oldDoc.profile) {
    // hack the type of content to update is user
    // since it has a profile...
    // since we do not have user update permission always return true
    return true ;
  }

  let newPerms = doc && Array.isArray(newDoc['permissions']) ? newDoc['permissions'].slice() : [];

  // is user allowed to update content?
  if(!exports.allowUpdateContent(user, newPerms, {})) {
    console.log("updated content - not allowed");
    return false ;
  }

  // user can update cuntent - check what field is being updated.
  // if (!isFieldEditable(user, oldDoc, newDoc)) {
  //   return false ;
  // }

  return true ;
}

exports.syncPermsArray = function(user, doc) {
  // console.log("syncPermsArray >>>>") ;
  // console.log(doc) ;
  return true ;
}

/* createContent
   returns a deffered promise: resolved / err
*/
exports.createContent = function(user, oldDoc, newDoc) {
  console.log("permissions createContent") ;
  console.log(JSON.stringify(newDoc)) ;
  let deffered = q.defer();

  if (newDoc.type == 'comment') {
    let Model = entityNameMap[newDoc.issue].mainModel;

    Model.findOne({'_id': oldDoc.issueId},function(err,doc){
      // console.log("doc") ;
      // console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      if(!exports.allowUpdateContent(user, newPerms, {})) {
        console.log("updated comments - not allowed");
        deffered.reject(exports.permError.denied + ":" + exports.permError.allowCreateContent);
        return deffered.promise;
      }
      console.log("updated comments - allowed");
      deffered.resolve("OK") ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  if(newDoc.type == 'assign' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to UPDATE watchers/permissions for new officeDoc assign.
    // console.log("assign");
    // console.log(JSON.stringify(newDoc));
    let Model = entityNameMap['officeDocument'].mainModel;
    let assigneeAdded = String(newDoc.userObj) ;

    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      // push assignee permissions
      let exist = exports.searchIdIndex(String(assigneeAdded),doc['permissions']) ;

      if(exist == null) {
//        console.log("adding assignee") ;
        let tmp = doc['permissions'].slice() ;
        tmp.push({"id":String(assigneeAdded),"level":"commenter"});
        doc['permissions'] = tmp ;

        // we need to add assignee as watcher if the assignee is not a watcher already
        let tmp2 = doc['watchers'] ;
        tmp2.push(String(doc.assign))
        doc['watchers'] = tmp2 ;
      }
      else {
        // update the level of assignee permission to editor
//        console.log("updating assignee") ;
        // console.log("perms array before:");
        // console.log(JSON.stringify(doc['permissions']));
        // console.log("--------------------");

        let tmp = doc['permissions'] ;
        tmp.splice(exist,1) ;
        tmp.push({"id":String(assigneeAdded),"level":"commenter"});
        doc['permissions'] = tmp ;
        // console.log("perms array after:");
        // console.log(JSON.stringify(doc['permissions']));
        // console.log("--------------------");
      }
      doc.save(function(err,result){
        //         todo
        //         if(err){
        //         }
        //         else{
        //         }
        //         return deffered.promise;
      });
      deffered.resolve(newDoc);
      return deffered.promise;
      })
    return deffered.promise;
  }
  if(newDoc.type == 'updateWatcher' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to UPDATE watchers/permissions for new officeDoc watchers.
    let newUid = newDoc.userObj._id ? newDoc.userObj._id : newDoc.userObj ;
    let Model = entityNameMap['officeDocument'].mainModel;
    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      // console.log("doc") ;
      // console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      console.log(newPerms) ;

      let watcherAdded = String(newUid) ;
      // console.log("updateWatcher:");
      // console.log(watcherAdded);

      let permLevel = String(watcherAdded) == String(newDoc.assign) ? 'commenter' : 'viewer' ;
      let watcherAddedPerms = {id:String(watcherAdded),level:permLevel} ; // default watcher perms

      newPerms.push(watcherAddedPerms);
      // console.log("updated perms array:");
      // console.log(JSON.stringify(newPerms));
      // console.log("--------------------");
      doc.permissions = newPerms ;
      // console.log(JSON.stringify(doc));

      doc.save(function(err,result){
//         todo
//         if(err){
//         }
//         else{
//         }
//         return deffered.promise;
      });
      deffered.resolve(newDoc) ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  if(newDoc.type == 'removeWatcher' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to REMOVE watchers/permissions for new officeDoc watchers.
    let newUid = newDoc.userObj._id ? newDoc.userObj._id : newDoc.userObj ;
//    console.log("remove watcher perms...");
    let Model = entityNameMap['officeDocument'].mainModel;
    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      console.log("doc");
      console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      console.log(newPerms) ;
      let watcherRemoved = String(newUid);
      let exist = exports.searchIdIndex(String(watcherRemoved),newPerms) ;
      // console.log("perms array before:");
      // console.log(JSON.stringify(doc['permissions']));
      // console.log("--------------------");

      if(exist == null) {
        // cannot find removed watcher - this shouldnt happen
        deffered.reject(exports.permError.system) ;
        return deffered.promise;
      }

      doc['permissions'].splice(exist,1) ;
      // console.log("updated doc['permissions'] array:");
      // console.log(JSON.stringify(doc['permissions']));
      // console.log("--------------------");
      doc.save(function(err,result){
//         todo
//         if(err){
//         }
//         else{
//         }
//         return deffered.promise;
      });
      deffered.resolve(newDoc) ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  deffered.resolve("OK") ;
  return deffered.promise;
}

function cleanArray(actual) {
  var newArray = new Array();
  for (var i = 0; i < actual.length; i++) {
    if (actual[i] && actual[i]!="null") {
      newArray.push(actual[i]);
    }
  }
  return newArray;
}

/*
  update permission array
  input: old Document, new Document
  output: permissions array
*/
exports.updatePermsArray = function(user, oldDoc, newDoc) {

  // console.log("updatePermsArray") ;
  // console.log("// check if the added watcher is the assignee:") ;
  // console.log(JSON.stringify(newDoc)) ;
  let oldWatchers = oldDoc.watchers instanceof Array ? oldDoc.watchers.map(function (item) {
    return String(item._doc._id) ;
  }) : [];

  let newWatchers = oldDoc.watchers instanceof Array ? newDoc.watchers.map(function (item) {
    return String(item);
  }) : [] ;

  newWatchers = cleanArray(newWatchers) ;

  // permissions array update needed? check if watchers changed.
  let diff = isDifferentArray(oldWatchers,newWatchers);
//  console.log("DIFF PERMS ARRAY:") ;
//  console.log(diff) ;
  if(diff == false) {
    return true ; // not different (users not added or removed), noop.
  }

  let newPerms = doc && Array.isArray(newDoc['permissions']) ? newDoc['permissions'].slice() : [];

  // is user allowed to update Perms?
  // console.log("newPerms:") ;
  // console.log(JSON.stringify(newPerms));
  if(!exports.allowUpdateWatcher(user, newPerms)) {
    console.log("updated perms array - not allowed");
    return false ;
  }

  // console.log("updating perms...") ;
  // console.log("original perms array:");
  // console.log(newPerms);
  // console.log("--------------------");

  // watcher added
  let watcherAdded = leftIntersect(newWatchers,oldWatchers) ;
  if(watcherAdded.length > 0) {
    // check if the added watcher is the assignee:
    // console.log("ASSIGNEE:") ;
    // console.log(JSON.stringify(newDoc.assign)) ;
    watcherAdded.forEach(function(watcher) {
      let permLevel = String(watcher) == String(newDoc.assign) ? 'commenter' : 'viewer' ;

      // console.log("LEVEL:") ;
      // console.log(permLevel) ;
      let watcherAddedPerms = {id:String(watcher),level:permLevel} ; // default watcher perms
      newPerms.push(watcherAddedPerms);
    })
  }
  // console.log("old-->");
  // console.log(JSON.stringify(oldWatchers));
  // console.log("new-->");
  // console.log(JSON.stringify(newWatchers));
  // console.log("added ->>");
  // console.log(JSON.stringify(watcherAdded));

  // watcher removed
  let watcherRemoved = leftIntersect(oldWatchers,newWatchers) ;
  // console.log("old-->");
  // console.log(JSON.stringify(oldWatchers));
  // console.log("new-->");
  // console.log(JSON.stringify(newWatchers));
  // console.log("removed ->>");
  // console.log(JSON.stringify(watcherRemoved));

  if(watcherRemoved.length > 0) {
    let index = exports.searchIdIndex(watcherRemoved[0], newDoc.permissions) ;// remove from permissions array
    newPerms.splice(index,1);
  }

  // console.log("updated perms array:");
  // console.log(newPerms);
  // console.log("--------------------");
  newDoc.permissions = newPerms ;
  return true ;
};

/*
  allow update watchers
*/
exports.allowUpdateWatcher = function(user, perms) {
  let uid =  String(user.user._id);
  let index = exports.searchIdIndex(String(uid),perms) ;
  if(index != null && perms[index].level == "editor") {
    console.log("permissions 'update watcher' true");
    return true ;
  }
  console.log("permissions 'update watcher' false");
  return false ;
};

/*
  allow assign
*/
exports.allowAssign = function(user, perms) {
  return exports.allowUpdateWatcher(user, perms) ;
};

/*
  allow update watchers level
*/
exports.allowUpdateWatcherLevel = function(user, perms) {
  return exports.allowUpdateWatcher(user, perms) ;
};

/*
  allow update of entity info.
*/
exports.allowUpdateContent = function(user, perms, field) {
  let uid =  String(user.user._id);
  console.log("perms:") ;
  console.log(JSON.stringify(perms)) ;
  let index = exports.searchIdIndex(String(uid),perms) ;
  let allowed = [] ;
  let permsAllowed = ["editor", "commenter"] ;
  if (index == null) {
    return false ;
  }

  allowed = permsAllowed.filter(function(element) {
      return element == perms[index].level; // user has perm
  }) ;

  // console.log("permissions 'update content': ");
  // console.log(allowed.length > 0 ? "true" : "false");

  return (allowed.length > 0) ;
};<commit_msg>permission rigorous perms test on update<commit_after>'use strict';

var q = require('q');

var TaskModel = require('../models/task.js');
var ProjectModel = require('../models/project.js');
var DiscussionModel = require('../models/discussion.js');
var OfficeDocumentsModel = require('../models/document.js');

var _ = require('lodash') ;
var entityNameMap = {
  'task': {
    mainModel: TaskModel,
//      archiveModel: TaskArchiveModel,
    name: 'task'
  },
  'project': {
    mainModel: ProjectModel,
//      archiveModel: ProjectArchiveModel,
    name: 'project'
  },
  'discussion': {
    mainModel: DiscussionModel,
//      archiveModel: DiscussionArchiveModel,
    name: 'discussion'
  },
  'officeDocuments':{
    mainModel: OfficeDocumentsModel,
//      archiveModel: OfficeDocumentsArchiveModel,
    name: 'officeDocument'
  },
  'officeDocument': {
    mainModel: OfficeDocumentsModel,
//      archiveModel: OfficeDocumentsArchiveModel,
    name: 'officeDocument'
  },
};

exports.permError =
{
  denied: "permission denied",
  granted: "permission granted",
  system: "update problem in occured",
  db: "db issue occured",
  allowUpdateWatcher: "update permissions for user",
  allowAssign: "assignee update",
  allowUpdateContent: "content update",
  allowCreateContent: "content create"
} ;

/*
    function: search if value exist in array in the format of arraItem.id
    return: the index in which the value exist, or null otherwise.
*/
exports.searchIdIndex = function(idVal, arr) {
  for (let i = 0; i < arr.length; i++) {
    if (String(arr[i].id) === String(idVal)) {
      //  console.log("user EXISTS!!!");
      return i;
    }
  }
  return null;
};

/*
    function: return the values that exist in both input arrays,
    and the values that exist only in the first array.
    input: 2 arrays.
    return: the index in which the value exist, or null otherwise.
*/
function leftIntersect(arr1, arr2) {
  let filtered = arr1.filter(function(i) {
    return arr2.indexOf(i) == -1 ? true : false;
  });
  return filtered;
}

/*
    function: check if there is an id in arr2, that is not present in 1.
    input: 2 arrays.
    return: true if id is found in arr2 and arr1, false otherwise.
*/
function isDifferentArray(arr1, arr2) {
  // console.log(JSON.stringify(arr1)) ;
  // console.log(JSON.stringify(arr2)) ;
  let result = [] ;

  if(arr1.length != arr2.length) {
    return true ;
  }

  arr1.forEach(function(a1) {
    if(arr2[a1]) {
      result++ ;
    }
  })

  arr2.forEach(function(a2) {
    if(arr1[a2]) {
      result++ ;
    }
  })

  if (result == 0) {
    return false ;
  }

  return true;
}

/*
  update permission array
  input: old Document, new Document
  output: permissions array
*/
exports.updateContent = function(user, oldDoc, newDoc) {

  console.log("updateContent" ) ;

  if(oldDoc.profile) {
    // hack the type of content to update is user
    // since it has a profile...
    // since we do not have user update permission always return true
    return true ;
  }

  let newPerms = newDoc && Array.isArray(newDoc['permissions']) ? newDoc['permissions'].slice() : [];

  // is user allowed to update content?
  if(!exports.allowUpdateContent(user, newPerms, {})) {
    console.log("updated content - not allowed");
    return false ;
  }

  // user can update cuntent - check what field is being updated.
  // if (!isFieldEditable(user, oldDoc, newDoc)) {
  //   return false ;
  // }

  return true ;
}

exports.syncPermsArray = function(user, doc) {
  // console.log("syncPermsArray >>>>") ;
  // console.log(doc) ;
  return true ;
}

/* createContent
   returns a deffered promise: resolved / err
*/
exports.createContent = function(user, oldDoc, newDoc) {
  console.log("permissions createContent") ;
  console.log(JSON.stringify(newDoc)) ;
  let deffered = q.defer();

  if (newDoc.type == 'comment') {
    let Model = entityNameMap[newDoc.issue].mainModel;

    Model.findOne({'_id': oldDoc.issueId},function(err,doc){
      console.log("doc") ;
      console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      if(!exports.allowUpdateContent(user, newPerms, {})) {
        console.log("updated comments - not allowed");
        deffered.reject(exports.permError.denied + ":" + exports.permError.allowCreateContent);
        return deffered.promise;
      }
      console.log("updated comments - allowed");
      deffered.resolve("OK") ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  if(newDoc.type == 'assign' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to UPDATE watchers/permissions for new officeDoc assign.
    // console.log("assign");
    // console.log(JSON.stringify(newDoc));
    let Model = entityNameMap['officeDocument'].mainModel;
    let assigneeAdded = String(newDoc.userObj) ;

    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      // push assignee permissions
      let exist = exports.searchIdIndex(String(assigneeAdded),doc['permissions']) ;

      if(exist == null) {
//        console.log("adding assignee") ;
        let tmp = doc['permissions'].slice() ;
        tmp.push({"id":String(assigneeAdded),"level":"commenter"});
        doc['permissions'] = tmp ;

        // we need to add assignee as watcher if the assignee is not a watcher already
        let tmp2 = doc['watchers'] ;
        tmp2.push(String(doc.assign))
        doc['watchers'] = tmp2 ;
      }
      else {
        // update the level of assignee permission to editor
//        console.log("updating assignee") ;
        // console.log("perms array before:");
        // console.log(JSON.stringify(doc['permissions']));
        // console.log("--------------------");

        let tmp = doc['permissions'] ;
        tmp.splice(exist,1) ;
        tmp.push({"id":String(assigneeAdded),"level":"commenter"});
        doc['permissions'] = tmp ;
        // console.log("perms array after:");
        // console.log(JSON.stringify(doc['permissions']));
        // console.log("--------------------");
      }
      doc.save(function(err,result){
        //         todo
        //         if(err){
        //         }
        //         else{
        //         }
        //         return deffered.promise;
      });
      deffered.resolve(newDoc);
      return deffered.promise;
      })
    return deffered.promise;
  }
  if(newDoc.type == 'updateWatcher' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to UPDATE watchers/permissions for new officeDoc watchers.
    let newUid = newDoc.userObj._id ? newDoc.userObj._id : newDoc.userObj ;
    let Model = entityNameMap['officeDocument'].mainModel;
    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      // console.log("doc") ;
      // console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      console.log(newPerms) ;

      let watcherAdded = String(newUid) ;
      // console.log("updateWatcher:");
      // console.log(watcherAdded);

      let permLevel = String(watcherAdded) == String(newDoc.assign) ? 'commenter' : 'viewer' ;
      let watcherAddedPerms = {id:String(watcherAdded),level:permLevel} ; // default watcher perms

      newPerms.push(watcherAddedPerms);
      // console.log("updated perms array:");
      // console.log(JSON.stringify(newPerms));
      // console.log("--------------------");
      doc.permissions = newPerms ;
      // console.log(JSON.stringify(doc));

      doc.save(function(err,result){
//         todo
//         if(err){
//         }
//         else{
//         }
//         return deffered.promise;
      });
      deffered.resolve(newDoc) ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  if(newDoc.type == 'removeWatcher' && newDoc.issue == 'officeDocuments') {
    // this piece of code is designed to REMOVE watchers/permissions for new officeDoc watchers.
    let newUid = newDoc.userObj._id ? newDoc.userObj._id : newDoc.userObj ;
//    console.log("remove watcher perms...");
    let Model = entityNameMap['officeDocument'].mainModel;
    Model.findOne({'_id': newDoc.issueId},function(err,doc){
      console.log("doc");
      console.log(doc) ;
      let newPerms = doc && Array.isArray(doc['permissions']) ? doc['permissions'].slice() : [];
      console.log(newPerms) ;
      let watcherRemoved = String(newUid);
      let exist = exports.searchIdIndex(String(watcherRemoved),newPerms) ;
      // console.log("perms array before:");
      // console.log(JSON.stringify(doc['permissions']));
      // console.log("--------------------");

      if(exist == null) {
        // cannot find removed watcher - this shouldnt happen
        deffered.reject(exports.permError.system) ;
        return deffered.promise;
      }

      doc['permissions'].splice(exist,1) ;
      // console.log("updated doc['permissions'] array:");
      // console.log(JSON.stringify(doc['permissions']));
      // console.log("--------------------");
      doc.save(function(err,result){
//         todo
//         if(err){
//         }
//         else{
//         }
//         return deffered.promise;
      });
      deffered.resolve(newDoc) ;
      return deffered.promise;
    })
    return deffered.promise;
  }
  deffered.resolve("OK") ;
  return deffered.promise;
}

function cleanArray(actual) {
  var newArray = new Array();
  for (var i = 0; i < actual.length; i++) {
    if (actual[i] && actual[i]!="null") {
      newArray.push(actual[i]);
    }
  }
  return newArray;
}

/*
  update permission array
  input: old Document, new Document
  output: permissions array
*/
exports.updatePermsArray = function(user, oldDoc, newDoc) {

  // console.log("updatePermsArray") ;
  // console.log("// check if the added watcher is the assignee:") ;
  // console.log(JSON.stringify(newDoc)) ;
  let oldWatchers = oldDoc.watchers instanceof Array ? oldDoc.watchers.map(function (item) {
    return String(item._doc._id) ;
  }) : [];

  let newWatchers = oldDoc.watchers instanceof Array ? newDoc.watchers.map(function (item) {
    return String(item);
  }) : [] ;

  newWatchers = cleanArray(newWatchers) ;

  // permissions array update needed? check if watchers changed.
  let diff = isDifferentArray(oldWatchers,newWatchers);
//  console.log("DIFF PERMS ARRAY:") ;
//  console.log(diff) ;
  if(diff == false) {
    return true ; // not different (users not added or removed), noop.
  }

  let newPerms = newDoc && Array.isArray(newDoc['permissions']) ? newDoc['permissions'].slice() : [];

  // is user allowed to update Perms?
  // console.log("newPerms:") ;
  // console.log(JSON.stringify(newPerms));
  if(!exports.allowUpdateWatcher(user, newPerms)) {
    console.log("updated perms array - not allowed");
    return false ;
  }

  // console.log("updating perms...") ;
  // console.log("original perms array:");
  // console.log(newPerms);
  // console.log("--------------------");

  // watcher added
  let watcherAdded = leftIntersect(newWatchers,oldWatchers) ;
  if(watcherAdded.length > 0) {
    // check if the added watcher is the assignee:
    // console.log("ASSIGNEE:") ;
    // console.log(JSON.stringify(newDoc.assign)) ;
    watcherAdded.forEach(function(watcher) {
      let permLevel = String(watcher) == String(newDoc.assign) ? 'commenter' : 'viewer' ;

      // console.log("LEVEL:") ;
      // console.log(permLevel) ;
      let watcherAddedPerms = {id:String(watcher),level:permLevel} ; // default watcher perms
      newPerms.push(watcherAddedPerms);
    })
  }
  // console.log("old-->");
  // console.log(JSON.stringify(oldWatchers));
  // console.log("new-->");
  // console.log(JSON.stringify(newWatchers));
  // console.log("added ->>");
  // console.log(JSON.stringify(watcherAdded));

  // watcher removed
  let watcherRemoved = leftIntersect(oldWatchers,newWatchers) ;
  // console.log("old-->");
  // console.log(JSON.stringify(oldWatchers));
  // console.log("new-->");
  // console.log(JSON.stringify(newWatchers));
  // console.log("removed ->>");
  // console.log(JSON.stringify(watcherRemoved));

  if(watcherRemoved.length > 0) {
    let index = exports.searchIdIndex(watcherRemoved[0], newDoc.permissions) ;// remove from permissions array
    newPerms.splice(index,1);
  }

  // console.log("updated perms array:");
  // console.log(newPerms);
  // console.log("--------------------");
  newDoc.permissions = newPerms ;
  return true ;
};

/*
  allow update watchers
*/
exports.allowUpdateWatcher = function(user, perms) {
  let uid =  String(user.user._id);
  let index = exports.searchIdIndex(String(uid),perms) ;
  if(index != null && perms[index].level == "editor") {
    console.log("permissions 'update watcher' true");
    return true ;
  }
  console.log("permissions 'update watcher' false");
  return false ;
};

/*
  allow assign
*/
exports.allowAssign = function(user, perms) {
  return exports.allowUpdateWatcher(user, perms) ;
};

/*
  allow update watchers level
*/
exports.allowUpdateWatcherLevel = function(user, perms) {
  return exports.allowUpdateWatcher(user, perms) ;
};

/*
  allow update of entity info.
*/
exports.allowUpdateContent = function(user, perms, field) {
  let uid =  String(user.user._id);
  console.log("perms:") ;
  console.log(JSON.stringify(perms)) ;
  let index = exports.searchIdIndex(String(uid),perms) ;
  let allowed = [] ;
  let permsAllowed = ["editor", "commenter"] ;
  if (index == null) {
    return false ;
  }

  allowed = permsAllowed.filter(function(element) {
      return element == perms[index].level; // user has perm
  }) ;

  // console.log("permissions 'update content': ");
  // console.log(allowed.length > 0 ? "true" : "false");

  return (allowed.length > 0) ;
};<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import {
  useCallback,
  useEffect,
  useState,
  useDebouncedCallback,
  useRef,
} from '@googleforcreators/react';
import { CSSTransition } from 'react-transition-group';
import { __ } from '@googleforcreators/i18n';
import { rgba } from 'polished';
import {
  Icons,
  useKeyDownEffect,
  Popup,
} from '@googleforcreators/design-system';
import { StoryPropTypes } from '@googleforcreators/elements';

const PLAY_BUTTON_SIZE = 82;
const ICON_SVG_SIZE = 72;
const PLAY_ABOVE_BREAKPOINT_WIDTH = 108;
const PLAY_ABOVE_BREAKPOINT_HEIGHT = 120;

const Controls = styled.div`
  position: absolute;
  z-index: 2;
  left: ${({ x }) => `${x}px`};
  top: ${({ y }) => `${y}px`};
  width: ${({ width }) => `${width}px`};
  height: ${({ height }) => `${height}px`};
  pointer-events: none;
`;

const ButtonWrapper = styled.div.attrs({ role: 'button', tabIndex: -1 })`
  ${({ isAbove }) =>
    !isAbove &&
    css`
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    `}
  cursor: pointer;
  pointer-events: initial;
  width: ${PLAY_BUTTON_SIZE}px;
  height: ${PLAY_BUTTON_SIZE}px;
  overflow: hidden;
  opacity: ${({ isAbove }) => (isAbove ? 1 : 0)};
  &.button-enter {
    opacity: 0;
  }
  &.button-enter-active,
  &.button-enter-done {
    opacity: 1;
    transition: opacity 100ms;
  }
  &.button-exit {
    opacity: 1;
  }
  &.button-exit-active,
  &.button-exit-done {
    opacity: 0;
    transition: opacity 100ms;
  }
`;

const iconCss = css`
  width: ${ICON_SVG_SIZE}px;
  height: ${ICON_SVG_SIZE}px;
  pointer-events: none;
  transform: translate(
    ${({ $isRTL }) =>
      ((PLAY_BUTTON_SIZE - ICON_SVG_SIZE) / 2) * ($isRTL ? -1 : 1)}px,
    ${(PLAY_BUTTON_SIZE - ICON_SVG_SIZE) / 2}px
  );
  color: ${({ theme }) => theme.colors.standard.white};
  filter: drop-shadow(
    0px 0px 10px ${({ theme }) => rgba(theme.colors.bg.primary, 0.4)}
  );
`;

const Play = styled(Icons.PlayFilled)`
  ${iconCss};
`;
const Pause = styled(Icons.StopFilled)`
  ${iconCss};
`;

const playAboveSpacing = {
  y: 22,
};

function PlayPauseButton({
  box,
  isActive = true,
  isTransforming = false,
  shouldResetOnEnd = true,
  elementRef,
  element,
  videoRef = null,
  isRTL,
  topOffset = 0,
}) {
  const hasVideoSrc = Boolean(element.resource.src);
  const isPlayAbove =
    element.width < PLAY_ABOVE_BREAKPOINT_WIDTH ||
    element.height < PLAY_ABOVE_BREAKPOINT_HEIGHT;
  const [hovering, setHovering] = useState(false);
  const [showControls, setShowControls] = useState(true);
  const [isPlaying, setIsPlaying] = useState(!isTransforming && hasVideoSrc);
  const { id } = element;
  const getVideoNode = useCallback(
    () =>
      videoRef ? videoRef.current : document.getElementById(`video-${id}`),
    [videoRef, id]
  );

  useEffect(() => {
    const videoNode = getVideoNode();
    if (!isActive) {
      if (videoNode) {
        videoNode.pause();
        videoNode.currentTime = 0;
      }
      setIsPlaying(false);
      setShowControls(false);
    }
    const syncTimer = setTimeout(() => {
      if (isActive && videoNode && !videoNode.paused && hasVideoSrc) {
        setIsPlaying(true);
      }
    });
    return () => clearTimeout(syncTimer);
  }, [getVideoNode, id, isActive, hasVideoSrc]);

  useEffect(() => {
    const videoNode = getVideoNode();
    if (videoNode && !videoNode.paused && !isTransforming) {
      videoNode.pause();
      setIsPlaying(false);
    }
  }, [getVideoNode, isTransforming]);

  useEffect(() => {
    const videoNode = getVideoNode();
    if (!videoNode || !shouldResetOnEnd) {
      return undefined;
    }

    const onVideoEnd = () => {
      videoNode.currentTime = 0;
      setIsPlaying(false);
      setShowControls(true);
    };
    videoNode.addEventListener('ended', onVideoEnd);
    return () => videoNode.removeEventListener('ended', onVideoEnd);
  }, [shouldResetOnEnd, getVideoNode, id]);

  const checkShowControls = useDebouncedCallback(() => {
    if (!isPlayAbove) {
      setShowControls(!isPlaying);
    }
  }, 2000);
  const checkMouseInBBox = useDebouncedCallback((evt) => {
    const node = elementRef.current;
    if (!node) {
      return;
    }
    const elementBBox = node.getBoundingClientRect();
    const isHovering =
      evt.clientX >= elementBBox.left &&
      evt.clientX <= elementBBox.right &&
      evt.clientY >= elementBBox.top &&
      evt.clientY <= elementBBox.bottom;
    setHovering(isHovering);
    setShowControls(true);
    checkShowControls();
    const videoNode = getVideoNode();
    if (videoNode) {
      setIsPlaying(!videoNode.paused);
    }
  }, 10);

  useEffect(() => {
    document.addEventListener('pointermove', checkMouseInBBox);
    return () => {
      document.removeEventListener('pointermove', checkMouseInBBox);
    };
  }, [checkMouseInBBox]);
  const handlePlayPause = (evt) => {
    evt.stopPropagation();
    const videoNode = getVideoNode();
    if (!videoNode) {
      return;
    }

    if (isPlaying) {
      videoNode.pause();
      setIsPlaying(false);
      setShowControls(true);
    } else {
      videoNode
        .play()
        .then(() => setIsPlaying(true))
        .catch(() => {});
    }
  };
  useKeyDownEffect(
    elementRef,
    {
      key: ['space'],
    },
    handlePlayPause,
    [handlePlayPause]
  );
  const nodeRef = useRef();
  if (!isActive) {
    return null;
  }

  const buttonLabel = isPlaying
    ? __('Click to pause', 'web-stories')
    : __('Click to play', 'web-stories');
  const TransitionWrapper = isPlayAbove
    ? ({ children }) => (
        <Popup
          isRTL={isRTL}
          anchor={elementRef}
          isOpen
          placement="top"
          spacing={playAboveSpacing}
          topOffset={topOffset}
        >
          {children}
        </Popup>
      )
    : CSSTransition;

  const Icon = isPlaying ? Pause : Play;
  return (
    <Controls data-controls-id={id} {...box}>
      {showControls && element.resource.src && (
        <TransitionWrapper
          in={hovering}
          appear
          nodeRef={nodeRef}
          classNames="button"
          timeout={100}
        >
          <ButtonWrapper
            ref={nodeRef}
            aria-label={buttonLabel}
            aria-pressed={isPlaying}
            key="wrapper"
            onMouseDown={handlePlayPause}
            isAbove={isPlayAbove}
          >
            <Icon $isRTL={isRTL} />
          </ButtonWrapper>
        </TransitionWrapper>
      )}
    </Controls>
  );
}

PlayPauseButton.propTypes = {
  box: StoryPropTypes.box.isRequired,
  isActive: PropTypes.bool,
  isTransforming: PropTypes.bool,
  shouldResetOnEnd: PropTypes.bool,
  elementRef: PropTypes.object.isRequired,
  element: StoryPropTypes.element.isRequired,
  videoRef: PropTypes.object,
  isRTL: PropTypes.bool,
  topOffset: PropTypes.number,
};

export default PlayPauseButton;
<commit_msg>Video: Improved handling of video play button (#11069)<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import {
  useCallback,
  useEffect,
  useState,
  useDebouncedCallback,
  useRef,
} from '@googleforcreators/react';
import { CSSTransition } from 'react-transition-group';
import { __ } from '@googleforcreators/i18n';
import { rgba } from 'polished';
import {
  Icons,
  useKeyDownEffect,
  Popup,
} from '@googleforcreators/design-system';
import { StoryPropTypes } from '@googleforcreators/elements';

const PLAY_BUTTON_SIZE = 82;
const ICON_SVG_SIZE = 72;
const PLAY_ABOVE_BREAKPOINT_WIDTH = 108;
const PLAY_ABOVE_BREAKPOINT_HEIGHT = 120;

const Controls = styled.div`
  position: absolute;
  z-index: 2;
  left: ${({ x }) => `${x}px`};
  top: ${({ y }) => `${y}px`};
  width: ${({ width }) => `${width}px`};
  height: ${({ height }) => `${height}px`};
  pointer-events: none;
`;

const ButtonWrapper = styled.div.attrs({ role: 'button', tabIndex: -1 })`
  ${({ isAbove }) =>
    !isAbove &&
    css`
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    `}
  cursor: pointer;
  pointer-events: initial;
  width: ${PLAY_BUTTON_SIZE}px;
  height: ${PLAY_BUTTON_SIZE}px;
  overflow: hidden;
  opacity: ${({ isAbove }) => (isAbove ? 1 : 0)};
  &.button-enter {
    opacity: 0;
  }
  &.button-enter-active,
  &.button-enter-done {
    opacity: 1;
    transition: opacity 100ms;
  }
  &.button-exit {
    opacity: 1;
  }
  &.button-exit-active,
  &.button-exit-done {
    opacity: 0;
    transition: opacity 100ms;
  }
`;

const iconCss = css`
  width: ${ICON_SVG_SIZE}px;
  height: ${ICON_SVG_SIZE}px;
  pointer-events: none;
  transform: translate(
    ${({ $isRTL }) =>
      ((PLAY_BUTTON_SIZE - ICON_SVG_SIZE) / 2) * ($isRTL ? -1 : 1)}px,
    ${(PLAY_BUTTON_SIZE - ICON_SVG_SIZE) / 2}px
  );
  color: ${({ theme }) => theme.colors.standard.white};
  filter: drop-shadow(
    0px 0px 10px ${({ theme }) => rgba(theme.colors.bg.primary, 0.4)}
  );
`;

const Play = styled(Icons.PlayFilled)`
  ${iconCss};
`;
const Pause = styled(Icons.StopFilled)`
  ${iconCss};
`;

const playAboveSpacing = {
  y: 22,
};

function PlayPauseButton({
  box,
  isActive = true,
  isTransforming = false,
  shouldResetOnEnd = true,
  elementRef,
  element,
  videoRef = null,
  isRTL,
  topOffset = 0,
}) {
  const hasVideoSrc = Boolean(element.resource.src);
  const isPlayAbove =
    element.width < PLAY_ABOVE_BREAKPOINT_WIDTH ||
    element.height < PLAY_ABOVE_BREAKPOINT_HEIGHT;
  const [hovering, setHovering] = useState(false);
  const [showControls, setShowControls] = useState(true);
  const [isPlaying, setIsPlaying] = useState(!isTransforming && hasVideoSrc);
  const { id } = element;
  const getVideoNode = useCallback(
    () =>
      videoRef ? videoRef.current : document.getElementById(`video-${id}`),
    [videoRef, id]
  );

  useEffect(() => {
    const videoNode = getVideoNode();
    if (!isActive) {
      if (videoNode) {
        videoNode.pause();
        videoNode.currentTime = 0;
      }
      setShowControls(false);
    }
  }, [getVideoNode, isActive]);

  useEffect(() => {
    const videoNode = getVideoNode();
    if (videoNode && !videoNode.paused && !isTransforming) {
      videoNode.pause();
    }
  }, [getVideoNode, isTransforming]);

  useEffect(() => {
    const videoNode = getVideoNode();
    if (!videoNode || !shouldResetOnEnd) {
      return undefined;
    }

    const onVideoEnd = () => {
      videoNode.currentTime = 0;
      setIsPlaying(false);
    };

    videoNode.addEventListener('ended', onVideoEnd);
    return () => videoNode.removeEventListener('ended', onVideoEnd);
  }, [shouldResetOnEnd, getVideoNode, id]);

  useEffect(() => {
    const videoNode = getVideoNode();
    if (!videoNode) {
      return undefined;
    }

    const onVideoPlay = () => setIsPlaying(true);
    const onVideoPause = () => setIsPlaying(false);

    videoNode.addEventListener('play', onVideoPlay);
    videoNode.addEventListener('pause', onVideoPause);
    return () => {
      videoNode.removeEventListener('play', onVideoPlay);
      videoNode.removeEventListener('pause', onVideoPause);
    };
  }, [getVideoNode, id]);

  const checkShowControls = useDebouncedCallback(() => {
    if (!isPlayAbove) {
      setShowControls(!isPlaying);
    }
  }, 2000);
  const checkMouseInBBox = useDebouncedCallback((evt) => {
    const node = elementRef.current;
    if (!node) {
      return;
    }
    const elementBBox = node.getBoundingClientRect();
    const isHovering =
      evt.clientX >= elementBBox.left &&
      evt.clientX <= elementBBox.right &&
      evt.clientY >= elementBBox.top &&
      evt.clientY <= elementBBox.bottom;
    setHovering(isHovering);
    setShowControls(true);
    checkShowControls();
  }, 10);

  useEffect(() => {
    document.addEventListener('pointermove', checkMouseInBBox);
    return () => {
      document.removeEventListener('pointermove', checkMouseInBBox);
    };
  }, [checkMouseInBBox]);
  const handlePlayPause = (evt) => {
    evt.stopPropagation();
    const videoNode = getVideoNode();
    if (!videoNode) {
      return;
    }

    if (isPlaying) {
      videoNode.pause();
      setShowControls(true);
    } else {
      videoNode.play().catch(() => {});
    }
  };
  useKeyDownEffect(
    elementRef,
    {
      key: ['space'],
    },
    handlePlayPause,
    [handlePlayPause]
  );
  const nodeRef = useRef();
  if (!isActive) {
    return null;
  }

  const buttonLabel = isPlaying
    ? __('Click to pause', 'web-stories')
    : __('Click to play', 'web-stories');
  const TransitionWrapper = isPlayAbove
    ? ({ children }) => (
        <Popup
          isRTL={isRTL}
          anchor={elementRef}
          isOpen
          placement="top"
          spacing={playAboveSpacing}
          topOffset={topOffset}
        >
          {children}
        </Popup>
      )
    : CSSTransition;

  const Icon = isPlaying ? Pause : Play;
  return (
    <Controls data-controls-id={id} {...box}>
      {showControls && element.resource.src && (
        <TransitionWrapper
          in={hovering}
          appear
          nodeRef={nodeRef}
          classNames="button"
          timeout={100}
        >
          <ButtonWrapper
            ref={nodeRef}
            aria-label={buttonLabel}
            aria-pressed={isPlaying}
            key="wrapper"
            onMouseDown={handlePlayPause}
            isAbove={isPlayAbove}
          >
            <Icon $isRTL={isRTL} />
          </ButtonWrapper>
        </TransitionWrapper>
      )}
    </Controls>
  );
}

PlayPauseButton.propTypes = {
  box: StoryPropTypes.box.isRequired,
  isActive: PropTypes.bool,
  isTransforming: PropTypes.bool,
  shouldResetOnEnd: PropTypes.bool,
  elementRef: PropTypes.object.isRequired,
  element: StoryPropTypes.element.isRequired,
  videoRef: PropTypes.object,
  isRTL: PropTypes.bool,
  topOffset: PropTypes.number,
};

export default PlayPauseButton;
<|endoftext|>


<commit_before>/*jshint -W097 */
'use strict';
const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const shelljs = require('shelljs');
const changeCase = require('change-case');

const { updateBoltRcConfig } = require('./update-boltrc');
const { addBoltPackage } = require('./add-bolt-package');

const currentBoltVersion = require('../../../../docs-site/package.json')
  .version;

var validateString = function(input) {
  if (typeof input !== 'string') {
    this.log(chalk.red('You must pass a valid string !'));
    return false;
  } else if (input.length === 0) {
    this.log(chalk.red('Tss Tss Tss, Write something !'));
    return false;
  }
  return true;
};

module.exports = class extends Generator {
  constructor(args, opts) {
    // Calling the super constructor
    // eslint-disable-next-line prefer-rest-params
    super(args, opts);
    this.option('noinstall');

    this.gitUrl = 'https://github.com/bolt-design-system/bolt';

    this.folders = {
      src: 'packages/components',
      patternLabFolder:
        'docs-site/src/pages/pattern-lab/_patterns/02-components',
      test: '__tests__',
    };

    this.boltVersion = currentBoltVersion;

    this.gitInfo = {
      name: shelljs
        .exec('git config user.name', { silent: true })
        .stdout.replace(/\n/g, ''),
      email: shelljs
        .exec('git config user.email', { silent: true })
        .stdout.replace(/\n/g, ''),
      github: shelljs
        .exec('git config github.user', { silent: true })
        .stdout.replace(/\n/g, ''),
    };
  }

  initializing() {
    this.log(yosay('Generating a new Bolt Design System component!'));
  }

  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'componentName',
        message:
          'What is the name of your Bolt component? (for example: `button`, `card`, carousel`, etc)',
        required: true,
        default: 'component',
        validate: function(input) {
          if (typeof input !== 'string') {
            this.log(chalk.red('You must pass a valid component name!'));
            return false;
          } else if (input.includes('_')) {
            this.log(
              chalk.red("Don't include underscores in your component name!"),
            );
            return false;
          } else if (input.includes(' ')) {
            this.log(
              chalk.red("Don't include any spaces in your component name!"),
            );
            return false;
          } else if (input.includes('bolt')) {
            this.log(
              chalk.red(
                "Make sure you aren't including the word `bolt` in your component name -- we take care of adding that automatically!",
              ),
            );
            return false;
          } else if (input.length === 0) {
            this.log(
              chalk.red('You need to name your new Bolt component something!'),
            );
            return false;
          }
          return true;
        }.bind(this),
        filter: function(input) {
          this.name = {
            original: input,
            camelCase: changeCase.camelCase(input),
            pascalCase: changeCase.pascalCase(input),
            snakeCase: changeCase.snakeCase(input),
            kebabCase: changeCase.paramCase(input),
            noCase: changeCase.noCase(input),
            titleCase: changeCase.titleCase(input),
          };

          return (
            input.charAt(0).toUpperCase() + input.slice(1).replace(' ', '-')
          );

        }.bind(this),
      },
      {
        type: 'input',
        name: 'description',
        message:
          'Could you write a sentence or two that describes your new Bolt component?',
        required: false,
        default: function(answers) {
          return `The ${
            this.name.noCase
          } component -- part of the Bolt Design System.`;
        }.bind(this),
        validate: function(input) {
          if (typeof input !== 'string') {
            this.log(chalk.red('You must pass a valid string !'));
            return false;
          }
          return true;
        }.bind(this),
      },
    ]).then(
      function(props) {
        this.props = props;
        this.props.name = this.name;
        this.props.gitUrl = this.gitUrl;
        this.props.boltVersion = this.boltVersion;
        this.props.gitInfo = this.gitInfo;
        this.props.packageName = `@bolt/components-${
          this.props.name.kebabCase
        }`;
        this.props.dest = `${this.folders.src}/bolt-${
          this.props.name.kebabCase
        }`;
        this.props.gitPath =
          this.gitUrl +
          '/tree/master/packages/components/bolt-' +
          this.props.name.kebabCase;
      }.bind(this),
    );
  }

  writing() {
    // component-specific scss
    this.fs.copyTpl(
      this.templatePath('component.scss'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.scss`,
      ),
      { props: this.props },
    );
    // index.scss in the component root
    this.fs.copyTpl(
      this.templatePath('index.component.scss'),
      this.destinationPath(`${this.props.dest}/index.scss`),
      { props: this.props },
    );

    // component-specific Twig template
    this.fs.copyTpl(
      this.templatePath('component.html.twig'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.twig`,
      ),
      { props: this.props },
    );

    // component-specific schema
    this.fs.copyTpl(
      this.templatePath('component.schema.yml'),
      this.destinationPath(
        `${this.props.dest}/${this.props.name.kebabCase}.schema.yml`,
      ),
      { props: this.props },
    );

    // component-specific web component base class
    this.fs.copyTpl(
      this.templatePath('component.js'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.js`,
      ),
      { props: this.props },
    );

    // component-specific web component root file -- polyfills before pulling in the main component JS
    this.fs.copyTpl(
      this.templatePath('index.component.js'),
      this.destinationPath(`${this.props.dest}/index.js`),
      { props: this.props },
    );

    // basic component Jest tests
    this.fs.copyTpl(
      this.templatePath('component.test.js'),
      this.destinationPath(
        `${this.folders.src}/bolt-${
          this.props.name.kebabCase
        }/__tests__/index.js`,
      ),
      { props: this.props },
    );

    // component README.md
    this.fs.copyTpl(
      this.templatePath('README.md'),
      this.destinationPath(
        `${this.folders.src}/bolt-${this.props.name.kebabCase}/README.md`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );

    // component package.json
    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath(
        `${this.folders.src}/bolt-${this.props.name.kebabCase}/package.json`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );

    // main component docs page in Pattern Lab
    this.fs.copyTpl(
      this.templatePath('component-docs.twig'),
      this.destinationPath(
        `${this.folders.patternLabFolder}/${this.props.name.kebabCase}/00-${
          this.props.name.kebabCase
        }-docs.twig`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );
  }

  install() {
    updateBoltRcConfig(this.props.packageName);
    addBoltPackage(this.props.packageName);

    shelljs.exec('yarn');
  }
};
<commit_msg>fix: update to automatically run generated files through Prettier automatically to prevent any linting issues<commit_after>/*jshint -W097 */
'use strict';
const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const shelljs = require('shelljs');
const changeCase = require('change-case');

const { updateBoltRcConfig } = require('./update-boltrc');
const { addBoltPackage } = require('./add-bolt-package');

const currentBoltVersion = require('../../../../docs-site/package.json')
  .version;

var validateString = function(input) {
  if (typeof input !== 'string') {
    this.log(chalk.red('You must pass a valid string !'));
    return false;
  } else if (input.length === 0) {
    this.log(chalk.red('Tss Tss Tss, Write something !'));
    return false;
  }
  return true;
};

module.exports = class extends Generator {
  constructor(args, opts) {
    // Calling the super constructor
    // eslint-disable-next-line prefer-rest-params
    super(args, opts);
    this.option('noinstall');

    this.gitUrl = 'https://github.com/bolt-design-system/bolt';

    this.folders = {
      src: 'packages/components',
      patternLabFolder:
        'docs-site/src/pages/pattern-lab/_patterns/02-components',
      test: '__tests__',
    };

    this.boltVersion = currentBoltVersion;

    this.gitInfo = {
      name: shelljs
        .exec('git config user.name', { silent: true })
        .stdout.replace(/\n/g, ''),
      email: shelljs
        .exec('git config user.email', { silent: true })
        .stdout.replace(/\n/g, ''),
      github: shelljs
        .exec('git config github.user', { silent: true })
        .stdout.replace(/\n/g, ''),
    };
  }

  initializing() {
    this.log(yosay('Generating a new Bolt Design System component!'));
  }

  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'componentName',
        message:
          'What is the name of your Bolt component? (for example: `button`, `card`, carousel`, etc)',
        required: true,
        default: 'component',
        validate: function(input) {
          if (typeof input !== 'string') {
            this.log(chalk.red('You must pass a valid component name!'));
            return false;
          } else if (input.includes('_')) {
            this.log(
              chalk.red("Don't include underscores in your component name!"),
            );
            return false;
          } else if (input.includes(' ')) {
            this.log(
              chalk.red("Don't include any spaces in your component name!"),
            );
            return false;
          } else if (input.includes('bolt')) {
            this.log(
              chalk.red(
                "Make sure you aren't including the word `bolt` in your component name -- we take care of adding that automatically!",
              ),
            );
            return false;
          } else if (input.length === 0) {
            this.log(
              chalk.red('You need to name your new Bolt component something!'),
            );
            return false;
          }
          return true;
        }.bind(this),
        filter: function(input) {
          this.name = {
            original: input,
            camelCase: changeCase.camelCase(input),
            pascalCase: changeCase.pascalCase(input),
            snakeCase: changeCase.snakeCase(input),
            kebabCase: changeCase.paramCase(input),
            noCase: changeCase.noCase(input),
            titleCase: changeCase.titleCase(input),
          };

          return (
            input.charAt(0).toUpperCase() + input.slice(1).replace(' ', '-')
          );

        }.bind(this),
      },
      {
        type: 'input',
        name: 'description',
        message:
          'Could you write a sentence or two that describes your new Bolt component?',
        required: false,
        default: function(answers) {
          return `The ${
            this.name.noCase
          } component -- part of the Bolt Design System.`;
        }.bind(this),
        validate: function(input) {
          if (typeof input !== 'string') {
            this.log(chalk.red('You must pass a valid string !'));
            return false;
          }
          return true;
        }.bind(this),
      },
    ]).then(
      function(props) {
        this.props = props;
        this.props.name = this.name;
        this.props.gitUrl = this.gitUrl;
        this.props.boltVersion = this.boltVersion;
        this.props.gitInfo = this.gitInfo;
        this.props.packageName = `@bolt/components-${
          this.props.name.kebabCase
        }`;
        this.props.dest = `${this.folders.src}/bolt-${
          this.props.name.kebabCase
        }`;
        this.props.gitPath =
          this.gitUrl +
          '/tree/master/packages/components/bolt-' +
          this.props.name.kebabCase;
      }.bind(this),
    );
  }

  writing() {
    // component-specific scss
    this.fs.copyTpl(
      this.templatePath('component.scss'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.scss`,
      ),
      { props: this.props },
    );
    // index.scss in the component root
    this.fs.copyTpl(
      this.templatePath('index.component.scss'),
      this.destinationPath(`${this.props.dest}/index.scss`),
      { props: this.props },
    );

    // component-specific Twig template
    this.fs.copyTpl(
      this.templatePath('component.html.twig'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.twig`,
      ),
      { props: this.props },
    );

    // component-specific schema
    this.fs.copyTpl(
      this.templatePath('component.schema.yml'),
      this.destinationPath(
        `${this.props.dest}/${this.props.name.kebabCase}.schema.yml`,
      ),
      { props: this.props },
    );

    // component-specific web component base class
    this.fs.copyTpl(
      this.templatePath('component.js'),
      this.destinationPath(
        `${this.props.dest}/src/${this.props.name.kebabCase}.js`,
      ),
      { props: this.props },
    );

    // component-specific web component root file -- polyfills before pulling in the main component JS
    this.fs.copyTpl(
      this.templatePath('index.component.js'),
      this.destinationPath(`${this.props.dest}/index.js`),
      { props: this.props },
    );

    // basic component Jest tests
    this.fs.copyTpl(
      this.templatePath('component.test.js'),
      this.destinationPath(
        `${this.folders.src}/bolt-${
          this.props.name.kebabCase
        }/__tests__/index.js`,
      ),
      { props: this.props },
    );

    // component README.md
    this.fs.copyTpl(
      this.templatePath('README.md'),
      this.destinationPath(
        `${this.folders.src}/bolt-${this.props.name.kebabCase}/README.md`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );

    // component package.json
    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath(
        `${this.folders.src}/bolt-${this.props.name.kebabCase}/package.json`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );

    // main component docs page in Pattern Lab
    this.fs.copyTpl(
      this.templatePath('component-docs.twig'),
      this.destinationPath(
        `${this.folders.patternLabFolder}/${this.props.name.kebabCase}/00-${
          this.props.name.kebabCase
        }-docs.twig`,
      ),
      {
        props: this.props,
        folders: this.folders,
      },
    );
  }

  install() {
    updateBoltRcConfig(this.props.packageName);
    addBoltPackage(this.props.packageName);

    shelljs.exec('yarn');

    shelljs.exec(
      `npx prettier ${this.folders.patternLabFolder}/${
        this.props.name.kebabCase
      }/**/*.{js,scss,json} --write`,
    );

    shelljs.exec(
      `npx prettier ${this.folders.src}/bolt-${
        this.props.name.kebabCase
      }/**/*.{js,scss,json} --write`,
    );
  }
};
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src',
      'protos'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2019 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/bigquery-data-transfer',
    theme: 'lumen',
    default: {
      outputSourceFiles: false
    }
  },
  markdown: {
    idInHeadings: true
  }
};
<commit_msg>chore: update jsdoc with macro license (#320)<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

'use strict';

module.exports = {
  opts: {
    readme: './README.md',
    package: './package.json',
    template: './node_modules/jsdoc-fresh',
    recurse: true,
    verbose: true,
    destination: './docs/'
  },
  plugins: [
    'plugins/markdown',
    'jsdoc-region-tag'
  ],
  source: {
    excludePattern: '(^|\\/|\\\\)[._]',
    include: [
      'build/src',
      'protos'
    ],
    includePattern: '\\.js$'
  },
  templates: {
    copyright: 'Copyright 2019 Google, LLC.',
    includeDate: false,
    sourceFiles: false,
    systemName: '@google-cloud/bigquery-data-transfer',
    theme: 'lumen',
    default: {
      outputSourceFiles: false
    }
  },
  markdown: {
    idInHeadings: true
  }
};
<|endoftext|>


<commit_before>//@ts-check
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
const {unparse} = require('@tryghost/members-csv');
const labs = require('../../../../../../shared/labs');

module.exports = {
    hasActiveStripeSubscriptions: createSerializer('hasActiveStripeSubscriptions', passthrough),

    browse: createSerializer('browse', paginatedMembers),
    read: createSerializer('read', singleMember),
    edit: createSerializer('edit', singleMember),
    add: createSerializer('add', singleMember),
    editSubscription: createSerializer('editSubscription', singleMember),
    createSubscription: createSerializer('createSubscription', singleMember),
    bulkDestroy: createSerializer('bulkDestroy', passthrough),
    bulkEdit: createSerializer('bulkEdit', bulkAction),
    exportCSV: createSerializer('exportCSV', exportCSV),

    importCSV: createSerializer('importCSV', passthrough),
    stats: createSerializer('stats', passthrough),
    memberStats: createSerializer('memberStats', passthrough),
    mrrStats: createSerializer('mrrStats', passthrough),
    subscriberStats: createSerializer('subscriberStats', passthrough),
    grossVolumeStats: createSerializer('grossVolumeStats', passthrough),
    activityFeed: createSerializer('activityFeed', passthrough)
};

/**
 * @template PageMeta
 *
 * @param {{data: import('bookshelf').Model[], meta: PageMeta}} page
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{members: SerializedMember[], meta: PageMeta}}
 */
function paginatedMembers(page, _apiConfig, frame) {
    return {
        members: page.data.map(model => serializeMember(model, frame.options)),
        meta: page.meta
    };
}

/**
 * @param {import('bookshelf').Model} model
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{members: SerializedMember[]}}
 */
function singleMember(model, _apiConfig, frame) {
    return {
        members: [serializeMember(model, frame.options)]
    };
}

/**
 * @param {object} bulkActionResult
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{bulk: SerializedBulkAction}}
 */
function bulkAction(bulkActionResult, _apiConfig, frame) {
    return {
        bulk: {
            action: frame.data.action,
            meta: {
                stats: {
                    successful: bulkActionResult.successful,
                    unsuccessful: bulkActionResult.unsuccessful
                },
                errors: bulkActionResult.errors,
                unsuccessfulData: bulkActionResult.unsuccessfulData
            }
        }
    };
}

/**
 * @template PageMeta
 *
 * @param {{data: import('bookshelf').Model[], meta: PageMeta}} page
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {string} - A CSV string
 */
function exportCSV(page, _apiConfig, frame) {
    debug('exportCSV');

    const members = page.data.map(model => serializeMember(model, frame.options));

    return unparse(members);
}

/**
 * @param {import('bookshelf').Model} member
 * @param {object} options
 *
 * @returns {SerializedMember}
 */
function serializeMember(member, options) {
    const json = member.toJSON ? member.toJSON(options) : member;

    const comped = json.status === 'comped';

    const subscriptions = json.subscriptions || [];

    const serialized = {
        id: json.id,
        uuid: json.uuid,
        email: json.email,
        name: json.name,
        note: json.note,
        geolocation: json.geolocation,
        subscribed: json.subscribed,
        created_at: json.created_at,
        updated_at: json.updated_at,
        labels: json.labels,
        subscriptions: subscriptions,
        avatar_image: json.avatar_image,
        comped: comped,
        email_count: json.email_count,
        email_opened_count: json.email_opened_count,
        email_open_rate: json.email_open_rate,
        email_recipients: json.email_recipients,
        status: json.status
    };

    if (labs.isSet('membersLastSeenFilter')) {
        serialized.last_seen_at = json.last_seen_at;
    }

    if (json.products) {
        serialized.products = json.products;
    }

    return serialized;
}

/**
 * @template Data
 * @param {Data} data
 * @returns Data
 */
function passthrough(data) {
    return data;
}

/**
 * @template Data
 * @template Response
 * @param {string} debugString
 * @param {(data: Data, apiConfig: APIConfig, frame: Frame) => Response} serialize - A function to serialize the data into an object suitable for API response
 *
 * @returns {(data: Data, apiConfig: APIConfig, frame: Frame) => void}
 */
function createSerializer(debugString, serialize) {
    return function serializer(data, apiConfig, frame) {
        debug(debugString);
        const response = serialize(data, apiConfig, frame);
        frame.response = response;
    };
}

/**
 * @typedef {Object} SerializedMember
 * @prop {string} id
 * @prop {string} uuid
 * @prop {string} email
 * @prop {string=} name
 * @prop {string=} note
 * @prop {null|string} geolocation
 * @prop {boolean} subscribed
 * @prop {string} created_at
 * @prop {string} updated_at
 * @prop {string[]} labels
 * @prop {SerializedMemberStripeSubscription[]} subscriptions
 * @prop {SerializedMemberProduct[]=} products
 * @prop {string} avatar_image
 * @prop {boolean} comped
 * @prop {number} email_count
 * @prop {number} email_opened_count
 * @prop {number} email_open_rate
 * @prop {null|SerializedEmailRecipient[]} email_recipients
 * @prop {'free'|'paid'} status
 */

/**
 * @typedef {Object} SerializedMemberProduct
 * @prop {string} id
 * @prop {string} name
 * @prop {string} slug
 */

/**
 * @typedef {Object} SerializedMemberStripeData
 * @prop {SerializedMemberStripeSubscription[]} subscriptions
 */

/**
 * @typedef {Object} SerializedMemberStripeSubscription
 *
 * @prop {string} id
 * @prop {string} status
 * @prop {string} start_date
 * @prop {string} default_payment_card_last4
 * @prop {string} current_period_end
 * @prop {boolean} cancel_at_period_end
 *
 * @prop {Object} customer
 * @prop {string} customer.id
 * @prop {null|string} customer.name
 * @prop {string} customer.email
 *
 * @prop {Object} price
 * @prop {string} price.id
 * @prop {string} price.nickname
 * @prop {number} price.amount
 * @prop {string} price.interval
 * @prop {string} price.currency
 *
 * @prop {Object} price.product
 * @prop {string} price.product.id
 * @prop {string} price.product.product_id
 */

/**
 * @typedef {Object} SerializedEmailRecipient
 *
 * @prop {string} id
 * @prop {string} email_id
 * @prop {string} batch_id
 * @prop {string} processed_at
 * @prop {string} delivered_at
 * @prop {string} opened_at
 * @prop {string} failed_at
 * @prop {string} member_uuid
 * @prop {string} member_email
 * @prop {string} member_name
 * @prop {SerializedEmail[]} email
 */

/**
 * @typedef {Object} SerializedEmail
 *
 * @prop {string} id
 * @prop {string} post_id
 * @prop {string} uuid
 * @prop {string} status
 * @prop {string} recipient_filter
 * @prop {null|string} error
 * @prop {string} error_data
 * @prop {number} email_count
 * @prop {number} delivered_count
 * @prop {number} opened_count
 * @prop {number} failed_count
 * @prop {string} subject
 * @prop {string} from
 * @prop {string} reply_to
 * @prop {string} html
 * @prop {string} plaintext
 * @prop {boolean} track_opens
 * @prop {string} created_at
 * @prop {string} created_by
 * @prop {string} updated_at
 * @prop {string} updated_by
 */

/**
 *
 * @typedef {Object} SerializedBulkAction
 *
 * @prop {string} action
 *
 * @prop {object} meta
 * @prop {object[]} meta.unsuccessfulData
 * @prop {Error[]} meta.errors
 * @prop {object} meta.stats
 *
 * @prop {number} meta.stats.successful
 * @prop {number} meta.stats.unsuccessful
 */

/**
 * @typedef {Object} APIConfig
 * @prop {string} docName
 * @prop {string} method
 */

/**
 * @typedef {Object<string, any>} Frame
 * @prop {Object} options
 */
<commit_msg>Removed unused members.stats serializer<commit_after>//@ts-check
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
const {unparse} = require('@tryghost/members-csv');
const labs = require('../../../../../../shared/labs');

module.exports = {
    hasActiveStripeSubscriptions: createSerializer('hasActiveStripeSubscriptions', passthrough),

    browse: createSerializer('browse', paginatedMembers),
    read: createSerializer('read', singleMember),
    edit: createSerializer('edit', singleMember),
    add: createSerializer('add', singleMember),
    editSubscription: createSerializer('editSubscription', singleMember),
    createSubscription: createSerializer('createSubscription', singleMember),
    bulkDestroy: createSerializer('bulkDestroy', passthrough),
    bulkEdit: createSerializer('bulkEdit', bulkAction),
    exportCSV: createSerializer('exportCSV', exportCSV),

    importCSV: createSerializer('importCSV', passthrough),
    memberStats: createSerializer('memberStats', passthrough),
    mrrStats: createSerializer('mrrStats', passthrough),
    subscriberStats: createSerializer('subscriberStats', passthrough),
    grossVolumeStats: createSerializer('grossVolumeStats', passthrough),
    activityFeed: createSerializer('activityFeed', passthrough)
};

/**
 * @template PageMeta
 *
 * @param {{data: import('bookshelf').Model[], meta: PageMeta}} page
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{members: SerializedMember[], meta: PageMeta}}
 */
function paginatedMembers(page, _apiConfig, frame) {
    return {
        members: page.data.map(model => serializeMember(model, frame.options)),
        meta: page.meta
    };
}

/**
 * @param {import('bookshelf').Model} model
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{members: SerializedMember[]}}
 */
function singleMember(model, _apiConfig, frame) {
    return {
        members: [serializeMember(model, frame.options)]
    };
}

/**
 * @param {object} bulkActionResult
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {{bulk: SerializedBulkAction}}
 */
function bulkAction(bulkActionResult, _apiConfig, frame) {
    return {
        bulk: {
            action: frame.data.action,
            meta: {
                stats: {
                    successful: bulkActionResult.successful,
                    unsuccessful: bulkActionResult.unsuccessful
                },
                errors: bulkActionResult.errors,
                unsuccessfulData: bulkActionResult.unsuccessfulData
            }
        }
    };
}

/**
 * @template PageMeta
 *
 * @param {{data: import('bookshelf').Model[], meta: PageMeta}} page
 * @param {APIConfig} _apiConfig
 * @param {Frame} frame
 *
 * @returns {string} - A CSV string
 */
function exportCSV(page, _apiConfig, frame) {
    debug('exportCSV');

    const members = page.data.map(model => serializeMember(model, frame.options));

    return unparse(members);
}

/**
 * @param {import('bookshelf').Model} member
 * @param {object} options
 *
 * @returns {SerializedMember}
 */
function serializeMember(member, options) {
    const json = member.toJSON ? member.toJSON(options) : member;

    const comped = json.status === 'comped';

    const subscriptions = json.subscriptions || [];

    const serialized = {
        id: json.id,
        uuid: json.uuid,
        email: json.email,
        name: json.name,
        note: json.note,
        geolocation: json.geolocation,
        subscribed: json.subscribed,
        created_at: json.created_at,
        updated_at: json.updated_at,
        labels: json.labels,
        subscriptions: subscriptions,
        avatar_image: json.avatar_image,
        comped: comped,
        email_count: json.email_count,
        email_opened_count: json.email_opened_count,
        email_open_rate: json.email_open_rate,
        email_recipients: json.email_recipients,
        status: json.status
    };

    if (labs.isSet('membersLastSeenFilter')) {
        serialized.last_seen_at = json.last_seen_at;
    }

    if (json.products) {
        serialized.products = json.products;
    }

    return serialized;
}

/**
 * @template Data
 * @param {Data} data
 * @returns Data
 */
function passthrough(data) {
    return data;
}

/**
 * @template Data
 * @template Response
 * @param {string} debugString
 * @param {(data: Data, apiConfig: APIConfig, frame: Frame) => Response} serialize - A function to serialize the data into an object suitable for API response
 *
 * @returns {(data: Data, apiConfig: APIConfig, frame: Frame) => void}
 */
function createSerializer(debugString, serialize) {
    return function serializer(data, apiConfig, frame) {
        debug(debugString);
        const response = serialize(data, apiConfig, frame);
        frame.response = response;
    };
}

/**
 * @typedef {Object} SerializedMember
 * @prop {string} id
 * @prop {string} uuid
 * @prop {string} email
 * @prop {string=} name
 * @prop {string=} note
 * @prop {null|string} geolocation
 * @prop {boolean} subscribed
 * @prop {string} created_at
 * @prop {string} updated_at
 * @prop {string[]} labels
 * @prop {SerializedMemberStripeSubscription[]} subscriptions
 * @prop {SerializedMemberProduct[]=} products
 * @prop {string} avatar_image
 * @prop {boolean} comped
 * @prop {number} email_count
 * @prop {number} email_opened_count
 * @prop {number} email_open_rate
 * @prop {null|SerializedEmailRecipient[]} email_recipients
 * @prop {'free'|'paid'} status
 */

/**
 * @typedef {Object} SerializedMemberProduct
 * @prop {string} id
 * @prop {string} name
 * @prop {string} slug
 */

/**
 * @typedef {Object} SerializedMemberStripeData
 * @prop {SerializedMemberStripeSubscription[]} subscriptions
 */

/**
 * @typedef {Object} SerializedMemberStripeSubscription
 *
 * @prop {string} id
 * @prop {string} status
 * @prop {string} start_date
 * @prop {string} default_payment_card_last4
 * @prop {string} current_period_end
 * @prop {boolean} cancel_at_period_end
 *
 * @prop {Object} customer
 * @prop {string} customer.id
 * @prop {null|string} customer.name
 * @prop {string} customer.email
 *
 * @prop {Object} price
 * @prop {string} price.id
 * @prop {string} price.nickname
 * @prop {number} price.amount
 * @prop {string} price.interval
 * @prop {string} price.currency
 *
 * @prop {Object} price.product
 * @prop {string} price.product.id
 * @prop {string} price.product.product_id
 */

/**
 * @typedef {Object} SerializedEmailRecipient
 *
 * @prop {string} id
 * @prop {string} email_id
 * @prop {string} batch_id
 * @prop {string} processed_at
 * @prop {string} delivered_at
 * @prop {string} opened_at
 * @prop {string} failed_at
 * @prop {string} member_uuid
 * @prop {string} member_email
 * @prop {string} member_name
 * @prop {SerializedEmail[]} email
 */

/**
 * @typedef {Object} SerializedEmail
 *
 * @prop {string} id
 * @prop {string} post_id
 * @prop {string} uuid
 * @prop {string} status
 * @prop {string} recipient_filter
 * @prop {null|string} error
 * @prop {string} error_data
 * @prop {number} email_count
 * @prop {number} delivered_count
 * @prop {number} opened_count
 * @prop {number} failed_count
 * @prop {string} subject
 * @prop {string} from
 * @prop {string} reply_to
 * @prop {string} html
 * @prop {string} plaintext
 * @prop {boolean} track_opens
 * @prop {string} created_at
 * @prop {string} created_by
 * @prop {string} updated_at
 * @prop {string} updated_by
 */

/**
 *
 * @typedef {Object} SerializedBulkAction
 *
 * @prop {string} action
 *
 * @prop {object} meta
 * @prop {object[]} meta.unsuccessfulData
 * @prop {Error[]} meta.errors
 * @prop {object} meta.stats
 *
 * @prop {number} meta.stats.successful
 * @prop {number} meta.stats.unsuccessful
 */

/**
 * @typedef {Object} APIConfig
 * @prop {string} docName
 * @prop {string} method
 */

/**
 * @typedef {Object<string, any>} Frame
 * @prop {Object} options
 */
<|endoftext|>


<commit_before>// ==UserScript==
// @name            Master Password+
// @author          xiaoxiaoflood
// @include         *
// @startup         UC.masterPasswordPlus.exec(win);
// @shutdown        UC.masterPasswordPlus.destroy();
// @onlyonce
// ==/UserScript==

UC.masterPasswordPlus = {
  exec: function (win) {
    if (!win.isChromeWindow || win != win.top)
      return;

    let document = win.document;

    let keyset =  _uc.createElement(document, 'keyset', { id: 'masterPassword-keyset' });
    let mainKeyset = document.getElementById('mainKeyset');
    if (mainKeyset)
      mainKeyset.insertAdjacentElement('afterend', keyset);
    else
      (document.body || document.documentElement).insertAdjacentElement('afterbegin', keyset);

    let k = _uc.createElement(document, 'key', {
      id: 'mpPk',
      modifiers: 'accel alt shift',
      key: 'W',
      oncommand: 'UC.masterPasswordPlus.lockAll()',
    });
    keyset.appendChild(k);

    let ovl = _uc.createElement(document, 'div', {
      id: 'mpPlus',
      style: 'position: fixed; display: none; width: 100%; height: 100%; top: 0; background-color: gray; z-index: 2147483647; cursor: pointer;'
    });
    document.documentElement.appendChild(ovl);

    let input = _uc.createElement(document, 'input', {
      id: 'mpPinput',
      type: 'password',
      style: 'border: 1px solid black; text-align: center; position: absolute; top:0; bottom: 0; left: 0; right: 0; margin: auto;'
    }, false);
    ovl.appendChild(input);

    input.addEventListener('blur', function () {
      setTimeout(() => {
        input.focus();
      });
    });

    if (this.mp.hasPassword && this.locked) {
      this.lock(document, win);
    }
  },

  mp: Cc['@mozilla.org/security/pk11tokendb;1'].getService(Ci.nsIPK11TokenDB).getInternalKeyToken(),

  keydownFunc: function (e) {
    let input = this.document.getElementById('mpPinput');
    if (e.key == 'Enter') {
      if (UC.masterPasswordPlus.mp.checkPassword(input.value)) {
        _uc.windows((doc, win) => {
          if (!'UC' in win || !win.isChromeWindow || win != win.top)
            return;
          doc.getElementById('mpPinput').value = '';
          doc.getElementById('mpPlus').style.display = 'none';
          [...doc.getElementsByTagName('panel')].forEach(el => el.style.display = '');
          win.titObs.disconnect();
          doc.title = win.titulo;
          win.removeEventListener('keydown', UC.masterPasswordPlus.keydownFunc, true);
          win.removeEventListener('activate', UC.masterPasswordPlus.setFocus);
          win.addEventListener('AppCommand', win.HandleAppCommandEvent, true);
        }, false);
        UC.masterPasswordPlus.locked = false;
      } else {
        input.value = '';
      }
    } else if ((e.key.length > 2 && // teclas digitáveis quase sempre =1, exceto acento seguido de char não acentuável, aí =2.
                e.code.length == 2 && // F1 a F9 possuem key.length =2, mas são as únicas com code.length = 2, demais são > (como KeyA).
                e.key != 'Dead' && // teclas de acento, que aguardam a tecla seguinte
                e.key != 'Backspace' && e.key != 'Delete' && e.key != 'ArrowLeft' && e.key != 'ArrowRight' && e.key != 'Home' && e.key != 'End') || e.altKey || (e.ctrlKey && e.code != 'KeyA')) {
      e.preventDefault();
    }
  },

  setFocus: function (e) {
    e.target.document.getElementById('mpPinput').focus();
  },

  onTitleChanged: function (win) {
    let document = win.document;
    let observer = new MutationObserver(mutationsList => {
      if (mutationsList[0].oldValue !== document.title && document.title != '🞻🞻🞻🞻🞻🞻') {
        win.titulo = document.title;
        document.title = '🞻🞻🞻🞻🞻🞻';
      }
    });

    if (document.getElementsByTagName('title').length)
      observer.observe(document.getElementsByTagName('title')[0], { childList: true, characterData: true });
    else
      observer.observe(document.documentElement, { attributeFilter: ['title'], attributeOldValue: true });

    return observer;
  },

  lock: function (doc, win) {
    win.addEventListener('keydown', UC.masterPasswordPlus.keydownFunc, true);
    let input = doc.getElementById('mpPinput');
    input.value = '';
    [...doc.getElementsByTagName('panel')].forEach(el => el.style.display = 'none');
    doc.getElementById('mpPlus').style.display = 'block';
    win.titulo = doc.title;
    doc.title = '🞻🞻🞻🞻🞻🞻';
    win.titObs = this.onTitleChanged(win);
    win.addEventListener('activate', this.setFocus);
    win.removeEventListener('AppCommand', win.HandleAppCommandEvent, true);
    input.focus();
  },

  lockAll: function () {
    if (!UC.masterPasswordPlus.mp.hasPassword)
      return;

    this.locked = true;
    _uc.windows((doc, win) => {
      if ('UC' in win && win.isChromeWindow && win == win.top)
        this.lock(doc, win);
    }, false);
  },

  locked: true,

  destroy: function () {
    _uc.windows((doc, win) => {
      if (!'UC' in win || !win.isChromeWindow || win != win.top)
        return;
      let mpPlus = doc.getElementById('mpPlus');
      if (mpPlus) {
        doc.getElementById('mpPlus').remove();
      }
      doc.getElementById('masterPassword-keyset').remove();
    }, false);
    delete UC.masterPasswordPlus;
  }
}
<commit_msg>mp+: menupopups win even against z-index: 2147483647;, so they need to be formally hidden.<commit_after>// ==UserScript==
// @name            Master Password+
// @author          xiaoxiaoflood
// @include         *
// @startup         UC.masterPasswordPlus.exec(win);
// @shutdown        UC.masterPasswordPlus.destroy();
// @onlyonce
// ==/UserScript==

UC.masterPasswordPlus = {
  exec: function (win) {
    if (!win.isChromeWindow || win != win.top)
      return;

    let document = win.document;

    let keyset =  _uc.createElement(document, 'keyset', { id: 'masterPassword-keyset' });
    let mainKeyset = document.getElementById('mainKeyset');
    if (mainKeyset)
      mainKeyset.insertAdjacentElement('afterend', keyset);
    else
      (document.body || document.documentElement).insertAdjacentElement('afterbegin', keyset);

    let k = _uc.createElement(document, 'key', {
      id: 'mpPk',
      modifiers: 'accel alt shift',
      key: 'W',
      oncommand: 'UC.masterPasswordPlus.lockAll()',
    });
    keyset.appendChild(k);

    let ovl = _uc.createElement(document, 'div', {
      id: 'mpPlus',
      style: 'position: fixed; display: none; width: 100%; height: 100%; top: 0; background-color: gray; z-index: 2147483647; cursor: pointer;'
    });
    document.documentElement.appendChild(ovl);

    let input = _uc.createElement(document, 'input', {
      id: 'mpPinput',
      type: 'password',
      style: 'border: 1px solid black; text-align: center; position: absolute; top:0; bottom: 0; left: 0; right: 0; margin: auto;'
    }, false);
    ovl.appendChild(input);

    input.addEventListener('blur', function () {
      setTimeout(() => {
        input.focus();
      });
    });

    if (this.mp.hasPassword && this.locked) {
      this.lock(document, win);
    }
  },

  mp: Cc['@mozilla.org/security/pk11tokendb;1'].getService(Ci.nsIPK11TokenDB).getInternalKeyToken(),

  keydownFunc: function (e) {
    let input = this.document.getElementById('mpPinput');
    if (e.key == 'Enter') {
      if (UC.masterPasswordPlus.mp.checkPassword(input.value)) {
        _uc.sss.unregisterSheet(UC.masterPasswordPlus.LOCKED_STYLE.url, UC.masterPasswordPlus.LOCKED_STYLE.type);

        _uc.windows((doc, win) => {
          if (!'UC' in win || !win.isChromeWindow || win != win.top)
            return;
          doc.getElementById('mpPinput').value = '';
          doc.getElementById('mpPlus').style.display = 'none';
          [...doc.getElementsByTagName('panel')].forEach(el => el.style.display = '');
          win.titObs.disconnect();
          doc.title = win.titulo;
          win.removeEventListener('keydown', UC.masterPasswordPlus.keydownFunc, true);
          win.removeEventListener('activate', UC.masterPasswordPlus.setFocus);
          win.addEventListener('AppCommand', win.HandleAppCommandEvent, true);
        }, false);
        UC.masterPasswordPlus.locked = false;
      } else {
        input.value = '';
      }
    } else if ((e.key.length > 2 && // teclas digitáveis quase sempre =1, exceto acento seguido de char não acentuável, aí =2.
                e.code.length == 2 && // F1 a F9 possuem key.length =2, mas são as únicas com code.length = 2, demais são > (como KeyA).
                e.key != 'Dead' && // teclas de acento, que aguardam a tecla seguinte
                e.key != 'Backspace' && e.key != 'Delete' && e.key != 'ArrowLeft' && e.key != 'ArrowRight' && e.key != 'Home' && e.key != 'End') || e.altKey || (e.ctrlKey && e.code != 'KeyA')) {
      e.preventDefault();
    }
  },

  setFocus: function (e) {
    e.target.document.getElementById('mpPinput').focus();
  },

  onTitleChanged: function (win) {
    let document = win.document;
    let observer = new MutationObserver(mutationsList => {
      if (mutationsList[0].oldValue !== document.title && document.title != '🞻🞻🞻🞻🞻🞻') {
        win.titulo = document.title;
        document.title = '🞻🞻🞻🞻🞻🞻';
      }
    });

    if (document.getElementsByTagName('title').length)
      observer.observe(document.getElementsByTagName('title')[0], { childList: true, characterData: true });
    else
      observer.observe(document.documentElement, { attributeFilter: ['title'], attributeOldValue: true });

    return observer;
  },

  lock: function (doc, win) {
    win.addEventListener('keydown', this.keydownFunc, true);
    let input = doc.getElementById('mpPinput');
    input.value = '';
    [...doc.getElementsByTagName('panel')].forEach(el => el.style.display = 'none');
    doc.getElementById('mpPlus').style.display = 'block';
    win.titulo = doc.title;
    doc.title = '🞻🞻🞻🞻🞻🞻';
    win.titObs = this.onTitleChanged(win);
    win.addEventListener('activate', this.setFocus);
    win.removeEventListener('AppCommand', win.HandleAppCommandEvent, true);
    input.focus();
  },

  lockAll: function () {
    if (!this.mp.hasPassword)
      return;

    this.locked = true;
    _uc.windows((doc, win) => {
      if ('UC' in win && win.isChromeWindow && win == win.top)
        this.lock(doc, win);
    }, false);

    _uc.sss.loadAndRegisterSheet(this.LOCKED_STYLE.url, this.LOCKED_STYLE.type);
  },

  locked: true,

  LOCKED_STYLE: {
    url: Services.io.newURI('data:text/css;charset=UTF-8,' + encodeURIComponent(`
      menupopup {
        display: none !important;
      }
    `)),
    type: _uc.sss.USER_SHEET
  },

  destroy: function () {
    _uc.windows((doc, win) => {
      if (!'UC' in win || !win.isChromeWindow || win != win.top)
        return;
      let mpPlus = doc.getElementById('mpPlus');
      if (mpPlus) {
        doc.getElementById('mpPlus').remove();
      }
      doc.getElementById('masterPassword-keyset').remove();
    }, false);
    delete UC.masterPasswordPlus;
  }
}

_uc.sss.loadAndRegisterSheet(UC.masterPasswordPlus.LOCKED_STYLE.url, UC.masterPasswordPlus.LOCKED_STYLE.type);
<|endoftext|>


<commit_before>// function definitions section
lx.lionixCRM.getConfigOptionEnvironment = function(opportunityId, currentValue, accountId) {
    if ($("#LionixCRM-environment").length == 0) {
        var method = "getLionixCRMConfigOption";
        var data = "method=" + method;
        data += "&option=" + "environment";
        // data += "&opportunityId=" + opportunityId;
        // data += "&currentValue=" + currentValue;
        // data += "&accountId=" + accountId;
        //data += "&use_adodb5="+"1";
        $.ajax({
            // beforeSend is a pre-request callback function that can be used to modify the jqXHR.
            beforeSend: function(jqXHR, settings) {
                console.groupCollapsed("Bussines logic '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', 'lx.lionixCRM.getConfigOptionEnvironment()', 'ajax beforeSend');
                console.log("*** start ***");
                console.log("beforeSend callback:", settings.url);
                console.groupEnd();
            }, //end beforeSend
            url: 'lxajax.php',
            type: 'GET',
            data: data,
            // success is a function to be called if the request succeeds.
            success: function(data, status, jqXHR) {
                console.groupCollapsed("Bussines logic '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', 'lx.lionixCRM.getConfigOptionEnvironment()', 'ajax success');
                console.log("success callback:", status);
                console.log("data:", data);
                if ($("#LionixCRM-environment").length == 0) {
                    if (data.toLowerCase() === 'testing') {
                        let pbclass = "progress-bar bg-info";
                        let probability = 100;
                        let text_bar = "Testing Environment"
                        $('#content').before('

' + text_bar + '

');
                    } else {
                        $('#content').before('
');
                    }
                    console.log("LionixCRM-environment added.");
                } else {
                    console.log("LionixCRM-environment exists.");
                }
                console.groupEnd();
            }, // end success
            // error is a function to be called if the request fails.
            error: function(jqXHR, status, error) {
                console.groupCollapsed("Bussines logic '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', 'lx.lionixCRM.getConfigOptionEnvironment()', 'ajax error');
                console.log("error callback:", status);
                console.log("Function lx.lionixCRM.getConfigOptionEnvironment error:", error);
                console.groupEnd();
            }, // end error
            // complete is a function to be called when the request finishes (after success and error callbacks are executed).
            // complete: function(jqXHR, status) {
            // }, // end complete
            datatype: "text"
        }); // end ajax
    } // end first if
} // end function
//eof

// This file containts all modules bussines logic
//Self-Invoking Anonymous Function Notation
// !function(){}(); // easy to read, the result is unimportant.
// (function(){})(); // like above but more parens.
// (function(){}()); // Douglas Crockford's style when you need function results.
// Further reading: http://javascript.crockford.com/code.html then search for invoked immediately
!function() {
    // create an observer instance
    // https://developer.mozilla.org/en/docs/Web/API/MutationObserver
    if ($("#LionixCRM-environment").length == 0) {
        console.groupCollapsed("Check environment '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', '!function()', 'initial');
        console.log('Running lx.lionixCRM.getConfigOptionEnvironment() function');
        console.groupEnd();
        lx.lionixCRM.getConfigOptionEnvironment();
    }
    var observer = new MutationObserver(function(mutations) {
        if (mutations) {
            if ($("#LionixCRM-environment").length == 0) {
                console.groupCollapsed("Check environment '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', '!function()', 'observer');
                console.log('Running lx.lionixCRM.getConfigOptionEnvironment() function');
                console.groupEnd();
                lx.lionixCRM.getConfigOptionEnvironment();
            }
            // if needed only once, you can stop observing with observer.disconnect();
            observer.disconnect();
        }
        // When need to find something special
        // mutations.forEach(function(mutation) {
        //     console.log('select2-lionixcrm mutation:', mutation);
        //     //     if (mutation.type == "attributes") {
        //     //         if (mutation.target.nodeName == "FORM" && mutation.target.id == "some-form-id") {
        //     //             //when found do your code
        //     //             //your code
        //     //             // if needed only once, you can stop observing with observer.disconnect();
        //     //             //observer.disconnect();
        //     //         }
        //     //     }
        // });
    });
    // Observer target
    var target = document.querySelector('#content');
    // configuration of the observer:
    // NOTE: At the very least, childList, attributes, or characterData must be set to true. Otherwise, "An invalid or illegal string was specified" error is thrown.
    var config = {
        attributes: true,
        childList: true,
        // characterData: true,
        subtree: true
    };
    // pass in the target node, as well as the observer options
    observer.observe(target, config);
    // end observer
}();
<commit_msg>refactor: getConfigOptionEnvironment split into checkEnvironment and then getEnvironment<commit_after>// function definitions section
lx.lionixCRM.getEnvironment = function() {
    if ($("#LionixCRM-environment").length == 0) {
        if (lx.lionixCRM.config.environment.toLowerCase() === 'testing') {
            let pbclass = "progress-bar bg-info";
            let probability = 100;
            let text_bar = "Testing Environment"
            $('#content').before('

' + text_bar + '

');
        } else {
            $('#content').before('
');
        }
        console.log("LionixCRM-environment added.");
    } else {
        console.log("LionixCRM-environment exists.");
    }
} // end function

lx.lionixCRM.checkEnvironment = function() {
    if (lx.lionixCRM.config.environment == undefined) {
        lx.lionixCRM.getConfigOption('environment').then(function() {
            lx.lionixCRM.getEnvironment()
        });
    } else {
        lx.lionixCRM.getEnvironment();
    }
} // end function
//eof

// This file containts all modules bussines logic
//Self-Invoking Anonymous Function Notation
// !function(){}(); // easy to read, the result is unimportant.
// (function(){})(); // like above but more parens.
// (function(){}()); // Douglas Crockford's style when you need function results.
// Further reading: http://javascript.crockford.com/code.html then search for invoked immediately
!function() {
    // create an observer instance
    // https://developer.mozilla.org/en/docs/Web/API/MutationObserver
    var observer = new MutationObserver(function(mutations) {
        if (mutations) {
            if ($("#LionixCRM-environment").length == 0) {
                console.groupCollapsed("Check environment '%s' '%s' '%s' '%s'", 'all modules', 'lx-check-crm-environment.js', '!function()', 'observer');
                console.log('Running lx.lionixCRM.checkEnvironment() function');
                console.groupEnd();
                lx.lionixCRM.checkEnvironment();
            }
            // if needed only once, you can stop observing with observer.disconnect();
            observer.disconnect();
        }
        // When need to find something special
        // mutations.forEach(function(mutation) {
        //     console.log('select2-lionixcrm mutation:', mutation);
        //     //     if (mutation.type == "attributes") {
        //     //         if (mutation.target.nodeName == "FORM" && mutation.target.id == "some-form-id") {
        //     //             //when found do your code
        //     //             //your code
        //     //             // if needed only once, you can stop observing with observer.disconnect();
        //     //             //observer.disconnect();
        //     //         }
        //     //     }
        // });
    });
    // Observer target
    var target = document.querySelector('#content');
    // configuration of the observer:
    // NOTE: At the very least, childList, attributes, or characterData must be set to true. Otherwise, "An invalid or illegal string was specified" error is thrown.
    var config = {
        attributes: true,
        childList: true,
        // characterData: true,
        subtree: true
    };
    // pass in the target node, as well as the observer options
    observer.observe(target, config);
    // end observer
}();
<|endoftext|>