<commit_before>import { combineReducers } from 'redux'
import { routeReducer } from 'redux-simple-router'
import { some, get, includes, omit, partition } from 'lodash'
import comments from './comments'
import commentsByPost from './commentsByPost'
import communities from './communities'
import communitiesByQuery from './communitiesByQuery'
import networks from './networks'
import networkEdits from './networkEdits'
import people from './people'
import peopleByQuery from './peopleByQuery'
import postEdits from './postEdits'
import postsByQuery from './postsByQuery'
import posts from './posts'
import projectsByQuery from './projectsByQuery'
import projects from './projects'
import projectEdits from './projectEdits'
import { UPDATE_PATH } from 'redux-simple-router'
import { appendUniq } from './util'

import {
  CANCEL_TYPEAHEAD,
  CREATE_COMMUNITY,
  CREATE_POST,
  CREATE_NETWORK,
  FETCH_ACTIVITY,
  FETCH_CURRENT_USER,
  FETCH_LIVE_STATUS,
  FETCH_COMMUNITIES,
  FETCH_INVITATIONS,
  FETCH_ONBOARDING,
  FETCH_PEOPLE,
  FETCH_POSTS,
  FETCH_PROJECTS,
  LOGIN,
  LOGOUT,
  MARK_ACTIVITY_READ,
  MARK_ALL_ACTIVITIES_READ_PENDING,
  NAVIGATE,
  NOTIFY,
  REMOVE_NOTIFICATION,
  RESET_ERROR,
  RESET_COMMUNITY_VALIDATION,
  RESET_NETWORK_VALIDATION,
  SEARCH,
  SEND_COMMUNITY_INVITATION,
  SEND_PROJECT_INVITE,
  SET_LOGIN_ERROR,
  SET_META_TAGS,
  SET_SIGNUP_ERROR,
  SIGNUP,
  TOGGLE_MAIN_MENU,
  TOGGLE_SHOW_ALL_COMMUNITIES,
  TOGGLE_USER_SETTINGS_SECTION,
  TYPEAHEAD,
  UPDATE_COMMUNITY_EDITOR,
  UPDATE_NETWORK_EDITOR,
  UPDATE_INVITATION_EDITOR,
  UPDATE_POST,
  UPDATE_PROJECT_INVITE,
  UPLOAD_IMAGE,
  VALIDATE_COMMUNITY_ATTRIBUTE,
  VALIDATE_COMMUNITY_ATTRIBUTE_PENDING,
  VALIDATE_NETWORK_ATTRIBUTE,
  VALIDATE_NETWORK_ATTRIBUTE_PENDING
} from '../actions'

const keyedCounter = (actionType, payloadKey, statePath = 'meta.cache.id') =>
  (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    if (type === actionType) {
      return {...state, [get(action, statePath)]: Number(payload[payloadKey])}
    }
    return state
  }

export default combineReducers({
  showAllCommunities: (state = false, action) => {
    if (action.type === TOGGLE_SHOW_ALL_COMMUNITIES) return !state
    return state
  },

  mainMenuOpened: (state = false, action) => {
    let { error, type } = action
    if (error) return state

    switch (type) {
      case TOGGLE_MAIN_MENU:
        return !state
      case NAVIGATE:
      case UPDATE_PATH:
        return false
    }

    return state
  },

  errors: (state = {}, action) => {
    let { error, type, payload, meta } = action

    switch (type) {
      case RESET_ERROR:
        return {...state, [meta.type]: null}
    }

    if (!error) return state

    return {...state, [type]: {error, payload, meta}}
  },

  routing: (state = {path: '/'}, action) => {
    if (action.error) return state

    switch (action.type) {
      case LOGOUT:
        return {path: '/login'}
      case NAVIGATE:
        return {path: action.payload}
      default:
        return routeReducer(state, action)
    }
  },

  login: (state = {}, action) => {
    let { type, payload, error } = action
    switch (type) {
      case LOGIN:
        if (error) return {error: payload.message}
        break
      case SET_LOGIN_ERROR:
        if (payload) return {error: payload}
    }
    return state
  },

  signup: (state = {}, action) => {
    let { type, payload, error } = action
    switch (type) {
      case SIGNUP:
        if (error) return {error: payload.message}
        break
      case SET_SIGNUP_ERROR:
        if (payload) return {error: payload}
    }
    return state
  },

  totalPostsByQuery: (state = {}, action) => {
    if (action.error) return state

    let { type, payload, meta } = action
    switch (type) {
      case FETCH_POSTS:
        return {...state, [meta.cache.id]: payload.posts_total}
    }
    return state
  },

  comments,
  commentsByPost,
  communities,
  communitiesByQuery,
  networks,
  networkEdits,
  people,
  peopleByQuery,
  posts,
  postsByQuery,
  postEdits,
  projects,
  projectsByQuery,
  projectEdits,

  pending: (state = {}, action) => {
    let { type, meta } = action

    let toggle = (targetType, useMeta) => {
      if (type === targetType) return {...state, [targetType]: false}
      if (type === targetType + '_PENDING') {
        return {...state, [targetType]: (useMeta && meta ? meta : true)}
      }
    }

    return toggle(FETCH_POSTS) ||
      toggle(FETCH_PEOPLE) ||
      toggle(UPLOAD_IMAGE, true) ||
      toggle(CREATE_POST) ||
      toggle(UPDATE_POST) ||
      toggle(FETCH_PROJECTS) ||
      toggle(CREATE_COMMUNITY) ||
      toggle(CREATE_NETWORK) ||
      toggle(FETCH_ACTIVITY) ||
      toggle(SEND_COMMUNITY_INVITATION) ||
      toggle(FETCH_INVITATIONS) ||
      toggle(SEND_PROJECT_INVITE) ||
      toggle(SEARCH) ||
      state
  },

  typeaheadMatches: (state = {}, action) => {
    let { error, type } = action
    if (error || !includes([TYPEAHEAD, CANCEL_TYPEAHEAD], type)) return state

    let { payload, meta: { id } } = action
    switch (type) {
      case TYPEAHEAD:
        return {...state, [id]: payload}
      case CANCEL_TYPEAHEAD:
        return {...state, [id]: []}
    }

    return state
  },

  totalPeopleByQuery: keyedCounter(FETCH_PEOPLE, 'people_total'),
  totalProjectsByQuery: keyedCounter(FETCH_PROJECTS, 'projects_total'),
  totalCommunitiesByQuery: keyedCounter(FETCH_COMMUNITIES, 'communities_total'),

  communityValidation: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case VALIDATE_COMMUNITY_ATTRIBUTE_PENDING:
        return {
          ...state,
          pending: {...state.pending, [meta.key]: true}
        }
      case VALIDATE_COMMUNITY_ATTRIBUTE:
        return {
          ...state,
          [meta.key]: payload,
          pending: {...state.pending, [meta.key]: false}
        }
      case RESET_COMMUNITY_VALIDATION:
        return {...state, [meta.key]: null}
    }

    return state
  },

  communityEditor: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) {
      switch (type) {
        case CREATE_COMMUNITY:
          return {
            ...state,
            errors: {...state.errors, server: `Server error: ${payload.message}`}
          }
        default:
          return state
      }
    }

    switch (type) {
      case UPDATE_COMMUNITY_EDITOR:
        return {
          ...state,
          [meta.subtree]: {...state[meta.subtree], ...payload}
        }
      case UPLOAD_IMAGE:
        if (meta.id !== 'new') break
        if (meta.subject === 'community-avatar') {
          return {
            ...state,
            community: {...state.community, avatar_url: payload}
          }
        } else if (meta.subject === 'community-banner') {
          return {
            ...state,
            community: {...state.community, banner_url: payload}
          }
        }
    }

    return state
  },

  networkValidation: (state = {}, action) => {
    let { type, payload, error, meta } = action
    let { id, key } = meta || {}
    if (error) return state

    switch (type) {
      case VALIDATE_NETWORK_ATTRIBUTE_PENDING:
        return {
          ...state,
          [id]: {...state[id], pending: {...(state[id] || {}).pending, [key]: true}}
        }
      case VALIDATE_NETWORK_ATTRIBUTE:
        return {
          ...state,
          [id]: {
            ...state[id],
            [key]: payload,
            pending: {...state[id].pending, [key]: false}
          }
        }
      case RESET_NETWORK_VALIDATION:
        return {
          ...state,
          [id]: {...state[id], [key]: null}
        }
    }

    return state
  },

  networkEditor: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) {
      switch (type) {
        case CREATE_NETWORK:
          return {
            ...state,
            errors: {...state.errors, server: `Server error: ${payload.message}`}
          }
        default:
          return state
      }
    }

    switch (type) {
      case UPDATE_NETWORK_EDITOR:
        return {
          ...state,
          [meta.subtree]: {...state[meta.subtree], ...payload}
        }
      case UPLOAD_IMAGE:
        if (meta.id !== 'new') break
        if (meta.subject === 'network-avatar') {
          return {
            ...state,
            network: {...state.network, avatar_url: payload}
          }
        } else if (meta.subject === 'network-banner') {
          return {
            ...state,
            network: {...state.network, banner_url: payload}
          }
        }
    }

    return state
  },

  projectInvite: (state = {}, action) => {
    let { type, payload, error, meta } = action
    let { id } = meta || {}
    if (error) return state
    switch (type) {
      case UPDATE_PROJECT_INVITE:
        return {
          ...state,
          [id]: {...state[id], ...payload}
        }
    }

    return state
  },

  userSettingsEditor: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case TOGGLE_USER_SETTINGS_SECTION:
        return {
          ...state,
          expand: {...state.expand, [payload]: meta.forceOpen || !get(state.expand, payload)}
        }
    }
    return state
  },

  activities: (state = [], action) => {
    let { type, payload, error, meta } = action
    if (error) return state
    let normalize = activity => {
      let comment = activity.comment
      if (!comment) {
        return activity
      }
      return {...omit(activity, 'comment'), comment_id: comment.id}
    }
    var activityId
    switch (type) {
      case FETCH_ACTIVITY:
        return state.concat(payload.items.map(normalize))
      case MARK_ACTIVITY_READ:
        activityId = meta.activityId
        return state.map(a => a.id === activityId ? {...a, unread: false} : a)
      case MARK_ALL_ACTIVITIES_READ_PENDING:
        return state.map(a => ({...a, unread: false}))
    }
    return state
  },

  totalActivities: (state = 0, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case FETCH_ACTIVITY:
        return payload.total
    }
    return state
  },

  metaTags: (state = {}, action) => {
    let { type, payload } = action
    if (type === SET_META_TAGS) {
      return payload
    }

    return state
  },

  invitations: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case FETCH_INVITATIONS:
        let { communityId, reset } = meta
        if (reset) return {...state, [communityId]: payload.items}
        return {
          ...state,
          [communityId]: [...(state[communityId] || []), ...payload.items]
        }
    }

    return state
  },

  totalInvitations: keyedCounter(FETCH_INVITATIONS, 'total', 'meta.communityId'),

  invitationEditor: (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case UPDATE_INVITATION_EDITOR:
        return {...state, [payload.field]: payload.value}
      case SEND_COMMUNITY_INVITATION:
        let { results } = payload
        let [ failures, successes ] = partition(results, r => r.error)
        let success, error
        let sl = successes.length
        if (sl > 0) {
          let pl = sl > 1
          success = `Sent invitation${pl ? 's' : ''} to ${sl} ${pl ? 'people' : 'person'}.`
        }
        if (some(failures)) {
          error = failures.map(r => `Couldn't send to ${r.email}: ${r.error}.`).join(' ')
        }
        return {...state, success, error}
    }
    return state
  },

  notifierMessages: (state = [], action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case NOTIFY:
        return [payload, ...state]
      case REMOVE_NOTIFICATION:
        return state.filter(n => n.id !== payload)
    }

    return state
  },

  onboarding: (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case FETCH_ONBOARDING:
        return payload
    }
    return state
  },

  searchResultsByQuery: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) return state
    switch (type) {
      case SEARCH:
        return appendUniq(state, meta.cache.id, payload.items)
    }
    return state
  },

  totalSearchResultsByQuery: keyedCounter(SEARCH, 'total'),

  pageTitle: (state = 'Hylo', action) => {
    let { type, payload } = action
    switch (type) {
      case FETCH_LIVE_STATUS:
      case FETCH_CURRENT_USER:
        let unreadCount = payload.new_notification_count
        return state.split(' (')[0] + (unreadCount > 0 ? ` (${unreadCount})` : '')
    }
    return state
  }

})
<commit_msg>don't set pagetitle on fetch_current_user<commit_after>import { combineReducers } from 'redux'
import { routeReducer } from 'redux-simple-router'
import { some, get, includes, omit, partition } from 'lodash'
import comments from './comments'
import commentsByPost from './commentsByPost'
import communities from './communities'
import communitiesByQuery from './communitiesByQuery'
import networks from './networks'
import networkEdits from './networkEdits'
import people from './people'
import peopleByQuery from './peopleByQuery'
import postEdits from './postEdits'
import postsByQuery from './postsByQuery'
import posts from './posts'
import projectsByQuery from './projectsByQuery'
import projects from './projects'
import projectEdits from './projectEdits'
import { UPDATE_PATH } from 'redux-simple-router'
import { appendUniq } from './util'

import {
  CANCEL_TYPEAHEAD,
  CREATE_COMMUNITY,
  CREATE_POST,
  CREATE_NETWORK,
  FETCH_ACTIVITY,
  FETCH_LIVE_STATUS,
  FETCH_COMMUNITIES,
  FETCH_INVITATIONS,
  FETCH_ONBOARDING,
  FETCH_PEOPLE,
  FETCH_POSTS,
  FETCH_PROJECTS,
  LOGIN,
  LOGOUT,
  MARK_ACTIVITY_READ,
  MARK_ALL_ACTIVITIES_READ_PENDING,
  NAVIGATE,
  NOTIFY,
  REMOVE_NOTIFICATION,
  RESET_ERROR,
  RESET_COMMUNITY_VALIDATION,
  RESET_NETWORK_VALIDATION,
  SEARCH,
  SEND_COMMUNITY_INVITATION,
  SEND_PROJECT_INVITE,
  SET_LOGIN_ERROR,
  SET_META_TAGS,
  SET_SIGNUP_ERROR,
  SIGNUP,
  TOGGLE_MAIN_MENU,
  TOGGLE_SHOW_ALL_COMMUNITIES,
  TOGGLE_USER_SETTINGS_SECTION,
  TYPEAHEAD,
  UPDATE_COMMUNITY_EDITOR,
  UPDATE_NETWORK_EDITOR,
  UPDATE_INVITATION_EDITOR,
  UPDATE_POST,
  UPDATE_PROJECT_INVITE,
  UPLOAD_IMAGE,
  VALIDATE_COMMUNITY_ATTRIBUTE,
  VALIDATE_COMMUNITY_ATTRIBUTE_PENDING,
  VALIDATE_NETWORK_ATTRIBUTE,
  VALIDATE_NETWORK_ATTRIBUTE_PENDING
} from '../actions'

const keyedCounter = (actionType, payloadKey, statePath = 'meta.cache.id') =>
  (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    if (type === actionType) {
      return {...state, [get(action, statePath)]: Number(payload[payloadKey])}
    }
    return state
  }

export default combineReducers({
  showAllCommunities: (state = false, action) => {
    if (action.type === TOGGLE_SHOW_ALL_COMMUNITIES) return !state
    return state
  },

  mainMenuOpened: (state = false, action) => {
    let { error, type } = action
    if (error) return state

    switch (type) {
      case TOGGLE_MAIN_MENU:
        return !state
      case NAVIGATE:
      case UPDATE_PATH:
        return false
    }

    return state
  },

  errors: (state = {}, action) => {
    let { error, type, payload, meta } = action

    switch (type) {
      case RESET_ERROR:
        return {...state, [meta.type]: null}
    }

    if (!error) return state

    return {...state, [type]: {error, payload, meta}}
  },

  routing: (state = {path: '/'}, action) => {
    if (action.error) return state

    switch (action.type) {
      case LOGOUT:
        return {path: '/login'}
      case NAVIGATE:
        return {path: action.payload}
      default:
        return routeReducer(state, action)
    }
  },

  login: (state = {}, action) => {
    let { type, payload, error } = action
    switch (type) {
      case LOGIN:
        if (error) return {error: payload.message}
        break
      case SET_LOGIN_ERROR:
        if (payload) return {error: payload}
    }
    return state
  },

  signup: (state = {}, action) => {
    let { type, payload, error } = action
    switch (type) {
      case SIGNUP:
        if (error) return {error: payload.message}
        break
      case SET_SIGNUP_ERROR:
        if (payload) return {error: payload}
    }
    return state
  },

  totalPostsByQuery: (state = {}, action) => {
    if (action.error) return state

    let { type, payload, meta } = action
    switch (type) {
      case FETCH_POSTS:
        return {...state, [meta.cache.id]: payload.posts_total}
    }
    return state
  },

  comments,
  commentsByPost,
  communities,
  communitiesByQuery,
  networks,
  networkEdits,
  people,
  peopleByQuery,
  posts,
  postsByQuery,
  postEdits,
  projects,
  projectsByQuery,
  projectEdits,

  pending: (state = {}, action) => {
    let { type, meta } = action

    let toggle = (targetType, useMeta) => {
      if (type === targetType) return {...state, [targetType]: false}
      if (type === targetType + '_PENDING') {
        return {...state, [targetType]: (useMeta && meta ? meta : true)}
      }
    }

    return toggle(FETCH_POSTS) ||
      toggle(FETCH_PEOPLE) ||
      toggle(UPLOAD_IMAGE, true) ||
      toggle(CREATE_POST) ||
      toggle(UPDATE_POST) ||
      toggle(FETCH_PROJECTS) ||
      toggle(CREATE_COMMUNITY) ||
      toggle(CREATE_NETWORK) ||
      toggle(FETCH_ACTIVITY) ||
      toggle(SEND_COMMUNITY_INVITATION) ||
      toggle(FETCH_INVITATIONS) ||
      toggle(SEND_PROJECT_INVITE) ||
      toggle(SEARCH) ||
      state
  },

  typeaheadMatches: (state = {}, action) => {
    let { error, type } = action
    if (error || !includes([TYPEAHEAD, CANCEL_TYPEAHEAD], type)) return state

    let { payload, meta: { id } } = action
    switch (type) {
      case TYPEAHEAD:
        return {...state, [id]: payload}
      case CANCEL_TYPEAHEAD:
        return {...state, [id]: []}
    }

    return state
  },

  totalPeopleByQuery: keyedCounter(FETCH_PEOPLE, 'people_total'),
  totalProjectsByQuery: keyedCounter(FETCH_PROJECTS, 'projects_total'),
  totalCommunitiesByQuery: keyedCounter(FETCH_COMMUNITIES, 'communities_total'),

  communityValidation: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case VALIDATE_COMMUNITY_ATTRIBUTE_PENDING:
        return {
          ...state,
          pending: {...state.pending, [meta.key]: true}
        }
      case VALIDATE_COMMUNITY_ATTRIBUTE:
        return {
          ...state,
          [meta.key]: payload,
          pending: {...state.pending, [meta.key]: false}
        }
      case RESET_COMMUNITY_VALIDATION:
        return {...state, [meta.key]: null}
    }

    return state
  },

  communityEditor: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) {
      switch (type) {
        case CREATE_COMMUNITY:
          return {
            ...state,
            errors: {...state.errors, server: `Server error: ${payload.message}`}
          }
        default:
          return state
      }
    }

    switch (type) {
      case UPDATE_COMMUNITY_EDITOR:
        return {
          ...state,
          [meta.subtree]: {...state[meta.subtree], ...payload}
        }
      case UPLOAD_IMAGE:
        if (meta.id !== 'new') break
        if (meta.subject === 'community-avatar') {
          return {
            ...state,
            community: {...state.community, avatar_url: payload}
          }
        } else if (meta.subject === 'community-banner') {
          return {
            ...state,
            community: {...state.community, banner_url: payload}
          }
        }
    }

    return state
  },

  networkValidation: (state = {}, action) => {
    let { type, payload, error, meta } = action
    let { id, key } = meta || {}
    if (error) return state

    switch (type) {
      case VALIDATE_NETWORK_ATTRIBUTE_PENDING:
        return {
          ...state,
          [id]: {...state[id], pending: {...(state[id] || {}).pending, [key]: true}}
        }
      case VALIDATE_NETWORK_ATTRIBUTE:
        return {
          ...state,
          [id]: {
            ...state[id],
            [key]: payload,
            pending: {...state[id].pending, [key]: false}
          }
        }
      case RESET_NETWORK_VALIDATION:
        return {
          ...state,
          [id]: {...state[id], [key]: null}
        }
    }

    return state
  },

  networkEditor: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) {
      switch (type) {
        case CREATE_NETWORK:
          return {
            ...state,
            errors: {...state.errors, server: `Server error: ${payload.message}`}
          }
        default:
          return state
      }
    }

    switch (type) {
      case UPDATE_NETWORK_EDITOR:
        return {
          ...state,
          [meta.subtree]: {...state[meta.subtree], ...payload}
        }
      case UPLOAD_IMAGE:
        if (meta.id !== 'new') break
        if (meta.subject === 'network-avatar') {
          return {
            ...state,
            network: {...state.network, avatar_url: payload}
          }
        } else if (meta.subject === 'network-banner') {
          return {
            ...state,
            network: {...state.network, banner_url: payload}
          }
        }
    }

    return state
  },

  projectInvite: (state = {}, action) => {
    let { type, payload, error, meta } = action
    let { id } = meta || {}
    if (error) return state
    switch (type) {
      case UPDATE_PROJECT_INVITE:
        return {
          ...state,
          [id]: {...state[id], ...payload}
        }
    }

    return state
  },

  userSettingsEditor: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case TOGGLE_USER_SETTINGS_SECTION:
        return {
          ...state,
          expand: {...state.expand, [payload]: meta.forceOpen || !get(state.expand, payload)}
        }
    }
    return state
  },

  activities: (state = [], action) => {
    let { type, payload, error, meta } = action
    if (error) return state
    let normalize = activity => {
      let comment = activity.comment
      if (!comment) {
        return activity
      }
      return {...omit(activity, 'comment'), comment_id: comment.id}
    }
    var activityId
    switch (type) {
      case FETCH_ACTIVITY:
        return state.concat(payload.items.map(normalize))
      case MARK_ACTIVITY_READ:
        activityId = meta.activityId
        return state.map(a => a.id === activityId ? {...a, unread: false} : a)
      case MARK_ALL_ACTIVITIES_READ_PENDING:
        return state.map(a => ({...a, unread: false}))
    }
    return state
  },

  totalActivities: (state = 0, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case FETCH_ACTIVITY:
        return payload.total
    }
    return state
  },

  metaTags: (state = {}, action) => {
    let { type, payload } = action
    if (type === SET_META_TAGS) {
      return payload
    }

    return state
  },

  invitations: (state = {}, action) => {
    let { type, payload, error, meta } = action
    if (error) return state

    switch (type) {
      case FETCH_INVITATIONS:
        let { communityId, reset } = meta
        if (reset) return {...state, [communityId]: payload.items}
        return {
          ...state,
          [communityId]: [...(state[communityId] || []), ...payload.items]
        }
    }

    return state
  },

  totalInvitations: keyedCounter(FETCH_INVITATIONS, 'total', 'meta.communityId'),

  invitationEditor: (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case UPDATE_INVITATION_EDITOR:
        return {...state, [payload.field]: payload.value}
      case SEND_COMMUNITY_INVITATION:
        let { results } = payload
        let [ failures, successes ] = partition(results, r => r.error)
        let success, error
        let sl = successes.length
        if (sl > 0) {
          let pl = sl > 1
          success = `Sent invitation${pl ? 's' : ''} to ${sl} ${pl ? 'people' : 'person'}.`
        }
        if (some(failures)) {
          error = failures.map(r => `Couldn't send to ${r.email}: ${r.error}.`).join(' ')
        }
        return {...state, success, error}
    }
    return state
  },

  notifierMessages: (state = [], action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case NOTIFY:
        return [payload, ...state]
      case REMOVE_NOTIFICATION:
        return state.filter(n => n.id !== payload)
    }

    return state
  },

  onboarding: (state = {}, action) => {
    let { type, payload, error } = action
    if (error) return state
    switch (type) {
      case FETCH_ONBOARDING:
        return payload
    }
    return state
  },

  searchResultsByQuery: (state = {}, action) => {
    let { type, payload, meta, error } = action
    if (error) return state
    switch (type) {
      case SEARCH:
        return appendUniq(state, meta.cache.id, payload.items)
    }
    return state
  },

  totalSearchResultsByQuery: keyedCounter(SEARCH, 'total'),

  pageTitle: (state = 'Hylo', action) => {
    let { type, payload } = action
    switch (type) {
      case FETCH_LIVE_STATUS:
        let unreadCount = payload.new_notification_count
        return state.split(' (')[0] + (unreadCount > 0 ? ` (${unreadCount})` : '')
    }
    return state
  }

})
<|endoftext|>


<commit_before>import { msg, meta } from 'mosi/core';
export let modes = {};

export function setModes (availableModes) {
  modes = availableModes;
}

export async function modeMessage ({ mode, action, arg }, src) {
  if (SAKA_DEBUG) {
    if (!modes[mode]) {
      throw Error(`Missing Mode ${mode}`);
    }
    if (!modes[mode].messages[action]) {
      throw Error(`Mode ${mode} is missing a handler for action ${action}`);
    }
  }
  return await modes[mode].messages[action](arg, src);
};

export let cachedClientSettings;

export function clientSettings (arg, src) {
  msg(src, 'clientSettings', cachedClientSettings);
}

export async function regenerateClientSettings () {
  cachedClientSettings = await generateClientSettings();
  if (SAKA_DEBUG) console.log('New clientSettings generated: ', cachedClientSettings);
  msg('client', 'clientSettings', cachedClientSettings);
}

async function generateClientSettings () {
  try {
    const {
      activeProfileGroup,
      profileGroups,
      settings,
      modes: modesConfig
    } = await browser.storage.local.get([
      'activeProfileGroup',
      'profileGroups',
      'settings',
      'modes'
    ]);
    const profileMap = profileGroups.find(({ name }) => name === activeProfileGroup).settings;
    const clientSettings = {};
    Object.entries(profileMap).forEach(([mode, profile]) => {
      const options = modesConfig.find(({ name }) => name === mode).options;
      const modeSettings = settings[mode].find(({ name }) => name === profileMap[mode]).settings;
      const { values, errors } = modes[mode].clientSettings(options, modeSettings);
      Object.assign(clientSettings, values);
    });
    return clientSettings;
  } catch (error) {
    console.error(error);
    return error.message;
  }
}

export function loadClient (_, src) {
  const { frameId, tabId } = meta(src);
  if (SAKA_DEBUG) {
    console.log(`Loading client: frame: ${frameId}, tab" ${tabId}`);
  }
  try {
    browser.tabs.executeScript(tabId, {
      file: '/content_script.js',
      frameId,
      runAt: 'document_start',
      matchAboutBlank: true
    });
  } catch (e) {
    if (SAKA_DEBUG) {
      console.error(e);
    }
  }

}
<commit_msg>fixed major bug that caused Saka Key to not load into preloaded pages. Preloaded pages are spawned when the user enters a URL into the URL bar. Attempting to execute the Saka client into these pages causes an error. Now, check for tab.index === -1 to make sure a page isn't preloaded, and listen for tabs.onReplaced to respawn the saka client bootloading process<commit_after>import { msg, meta } from 'mosi/core';
export let modes = {};

export function setModes (availableModes) {
  modes = availableModes;
}

export async function modeMessage ({ mode, action, arg }, src) {
  if (SAKA_DEBUG) {
    if (!modes[mode]) {
      throw Error(`Missing Mode ${mode}`);
    }
    if (!modes[mode].messages[action]) {
      throw Error(`Mode ${mode} is missing a handler for action ${action}`);
    }
  }
  return await modes[mode].messages[action](arg, src);
};

export let cachedClientSettings;

/**
 * When a client requests settings, messages back the current client settings.
 * Future: domain lookups to determine settings to message back
 */
export function clientSettings (arg, src) {
  msg(src, 'clientSettings', cachedClientSettings);
}

export async function regenerateClientSettings () {
  cachedClientSettings = await generateClientSettings();
  if (SAKA_DEBUG) console.log('New clientSettings generated: ', cachedClientSettings);
  msg('client', 'clientSettings', cachedClientSettings);
}

async function generateClientSettings () {
  try {
    const {
      activeProfileGroup,
      profileGroups,
      settings,
      modes: modesConfig
    } = await browser.storage.local.get([
      'activeProfileGroup',
      'profileGroups',
      'settings',
      'modes'
    ]);
    const profileMap = profileGroups.find(({ name }) => name === activeProfileGroup).settings;
    const clientSettings = {};
    Object.entries(profileMap).forEach(([mode, profile]) => {
      const options = modesConfig.find(({ name }) => name === mode).options;
      const modeSettings = settings[mode].find(({ name }) => name === profileMap[mode]).settings;
      const { values, errors } = modes[mode].clientSettings(options, modeSettings);
      Object.assign(clientSettings, values);
    });
    return clientSettings;
  } catch (error) {
    console.error(error);
    return error.message;
  }
}

export function loadClient (_, src) {
  const { sender: { frameId, tab: { index: tabIndex, id: tabId } } } = meta(src);
  // tabIndex === -1 indicates the tab is preloaded but not a full tab
  // attempting to execute into it is an error
  // http://stackoverflow.com/questions/43665470/cannot-call-chrome-tabs-executescript-into-preloaded-tab-is-this-a-bug-in-chr
  if (tabIndex === -1) {
    if (SAKA_DEBUG) {
      console.log(`NOT loading client into preloaded tab: frame: ${frameId}, tab: ${tabId}`);
    }
  } else {
    if (SAKA_DEBUG) {
      console.log(`Loading client: frame: ${frameId}, tab: ${tabId}`);
    }
    browser.tabs.executeScript(tabId, {
      file: '/content_script.js',
      frameId,
      runAt: 'document_start',
      matchAboutBlank: true
    });
  }
}

/**
 * Requests for the full Saka Client by the client loaders of preloaded tabs are
 * denied because attempting to execute within them would cause errors.
 * http://stackoverflow.com/questions/43665470/cannot-call-chrome-tabs-executescript-into-preloaded-tab-is-this-a-bug-in-chr
 * Instead, the background page listens for the tabs.onReplaced event and executes
 * content_script_loader.js into all frames, which then launches another request for
 * the full Saka Client
 * @param {number} addedTabId
 * @param {number} removedTabId
 */
function reloadClientLoaders (addedTabId, removedTabId) {
  if (SAKA_DEBUG) {
    console.log(`Tab id changed from ${removedTabId} to ${addedTabId}. Reloading content_script_loader.js`);
  }
  browser.tabs.executeScript(addedTabId, {
    file: '/content_script_loader.js',
    allFrames: true,
    runAt: 'document_start',
    matchAboutBlank: true
  });
}
chrome.tabs.onReplaced.addListener(reloadClientLoaders);
<|endoftext|>


<commit_before>import LinkMany from './linkTypes/linkMany.js';
import LinkManyMeta from './linkTypes/linkManyMeta.js';
import LinkOne from './linkTypes/linkOne.js';
import LinkOneMeta from './linkTypes/linkOneMeta.js';
import LinkResolve from './linkTypes/linkResolve.js';
import {linkStorage as linkStorageSymbol} from './symbols.js';
import ConfigSchema from './config.schema.js';
import createFieldSchema from './lib/createFieldSchema.js';
import smartArguments from './linkTypes/lib/smartArguments';

export default class Linker {
    /**
     * @param mainCollection
     * @param linkName
     * @param linkConfig
     */
    constructor(mainCollection, linkName, linkConfig) {
        this.mainCollection = mainCollection;
        this.linkConfig = linkConfig;
        this.linkName = linkName;

        // check linkName must not exist in schema
        this._validateAndClean();
        this._extendSchema();

        // initialize cascade removal hooks.
        if (linkConfig.autoremove) {
            this._initAutoremove();
        }

        if (this.isVirtual()) {
            // if it's a virtual field make sure that when this is deleted, it will be removed from the references
            if (!linkConfig.autoremove) {
                this._handleReferenceRemovalForVirtualLinks();
            }
        } else {
            this._initIndex();
        }
    }

    /**
     * Values which represent for the relation a single link
     * @returns {string[]}
     */
    get oneTypes() {
        return ['one', '1', 'single'];
    }

    /**
     * Returns the strategies: one, many, one-meta, many-meta
     * @returns {string}
     */
    get strategy() {
        if (this.isResolver()) {
            return 'resolver';
        }

        let strategy = this.isMany() ? 'many' : 'one';
        if (this.linkConfig.metadata) {
            strategy += '-meta';
        }

        return strategy;
    }

    /**
     * Returns the field name in the document where the actual relationships are stored.
     * @returns string
     */
    get linkStorageField() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.linkStorageField;
        }

        return this.linkConfig.field;
    }

    /**
     * The collection that is linked with the current collection
     * @returns Mongo.Collection
     */
    getLinkedCollection() {
        // if our link is a resolver, then we really don't have a linked collection.
        if (this.isResolver()) {
            return null;
        }

        return this.linkConfig.collection;
    }

    /**
     * If the relationship for this link is of "many" type.
     */
    isMany() {
        return !this.isSingle();
    }

    /**
     * If the relationship for this link contains metadata
     */
    isMeta() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.isMeta();
        }

        return !!this.linkConfig.metadata;
    }

    /**
     * @returns {boolean}
     */
    isSingle() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.isSingle();
        }

        return _.contains(this.oneTypes, this.linkConfig.type);
    }

    /**
     * @returns {boolean}
     */
    isVirtual() {
        return !!this.linkConfig.inversedBy;
    }

    /**
     * @returns {boolean}
     */
    isResolver() {
        return _.isFunction(this.linkConfig.resolve);
    }

    /**
     * Should return a single result.
     */
    isOneResult() {
        return (
            (this.isVirtual() && this.linkConfig.relatedLinker.linkConfig.unique)
            || (!this.isVirtual() && this.isSingle())
        );
    }

    /**
     * @param object
     * @param collection To impersonate the getLinkedCollection() of the "Linker"
     *
     * @returns {LinkOne|LinkMany|LinkManyMeta|LinkOneMeta|LinkResolve}
     */
    createLink(object, collection = null) {
        let helperClass = this._getHelperClass();

        return new helperClass(this, object, collection);
    }

    /**
     * @returns {*}
     * @private
     */
    _validateAndClean() {
        if (!this.isResolver()) {
            if (!this.linkConfig.collection) {
                throw new Meteor.Error('invalid-config', `For the link ${this.linkName} you did not provide a collection. Collection is mandatory for non-resolver links.`)
            }

            if (typeof(this.linkConfig.collection) === 'string') {
                const collectionName = this.linkConfig.collection;
                this.linkConfig.collection = Mongo.Collection.get(collectionName);

                if (!this.linkConfig.collection) {
                    throw new Meteor.Error('invalid-collection', `Could not find a collection with the name: ${collectionName}`);
                }
            }

            if (this.isVirtual()) {
                return this._prepareVirtual();
            } else {
                if (!this.linkConfig.type) {
                    this.linkConfig.type = 'one';
                }

                if (!this.linkConfig.field) {
                    this.linkConfig.field = this._generateFieldName();
                } else {
                    if (this.linkConfig.field == this.linkName) {
                        throw new Meteor.Error('invalid-config', `For the link ${this.linkName} you must not use the same name for the field, otherwise it will cause conflicts when fetching data`);
                    }
                }
            }
        }

        ConfigSchema.validate(this.linkConfig);
    }

    /**
     * We need to apply same type of rules in this case.
     * @private
     */
    _prepareVirtual() {
        const {collection, inversedBy} = this.linkConfig;
        let linker = collection.getLinker(inversedBy);

        if (!linker) {
            // it is possible that the collection doesn't have a linker created yet.
            // so we will create it on startup after all links have been defined
            Meteor.startup(() => {
                linker = collection.getLinker(inversedBy);
                if (!linker) {
                    throw new Meteor.Error(`You tried setting up an inversed link "${inversedBy}" in "${this.mainCollection._name}" to the "${collection._name}" collection that does not have any links defined`)
                } else {
                    this._setupVirtualConfig(linker);
                }
            })
        } else {
            this._setupVirtualConfig(linker);
        }
    }

    /**
     * @param linker
     * @private
     */
    _setupVirtualConfig(linker) {
        const virtualLinkConfig = linker.linkConfig;

        if (!virtualLinkConfig) {
            throw new Meteor.Error(`There is no link-config for the related collection on ${inversedBy}. Make sure you added the direct links before specifying virtual ones.`)
        }

        _.extend(this.linkConfig, {
            metadata: virtualLinkConfig.metadata,
            relatedLinker: linker
        });
    }

    /**
     * Depending on the strategy, we create the proper helper class
     * @private
     */
    _getHelperClass() {
        switch (this.strategy) {
            case 'resolver':
                return LinkResolve;
            case 'many-meta':
                return LinkManyMeta;
            case 'many':
                return LinkMany;
            case 'one-meta':
                return LinkOneMeta;
            case 'one':
                return LinkOne;
        }

        throw new Meteor.Error('invalid-strategy', `${this.strategy} is not a valid strategy`);
    }

    /**
     * Extends the schema of the collection.
     * @private
     */
    _extendSchema() {
        if (this.isVirtual() || this.isResolver()) {
            return;
        }

        if (this.mainCollection.simpleSchema && this.mainCollection.simpleSchema()) {
            const schema = createFieldSchema(this.isMany(), this.linkConfig.metadata);

            this.mainCollection.attachSchema({
                [this.linkConfig.field]: schema
            });
        }
    }

    /**
     * If field name not present, we generate it.
     * @private
     */
    _generateFieldName() {
        let cleanedCollectionName = this.linkConfig.collection._name.replace(/\./g, '_');
        let defaultFieldPrefix = this.linkName + '_' + cleanedCollectionName;

        switch (this.strategy) {
            case 'many-meta':
                return `${defaultFieldPrefix}_metas`;
            case 'many':
                return `${defaultFieldPrefix}_ids`;
            case 'one-meta':
                return `${defaultFieldPrefix}_meta`;
            case 'one':
                return `${defaultFieldPrefix}_id`;
        }
    }

    /**
     * When a link that is declared virtual is removed, the reference will be removed from every other link.
     * @private
     */
    _handleReferenceRemovalForVirtualLinks() {
        this.mainCollection.after.remove((userId, doc) => {
            let accessor = this.createLink(doc);

            _.each(accessor.fetch(), linkedObj => {
                const {relatedLinker} = this.linkConfig;
                // We do this check, to avoid self-referencing hell when defining virtual links
                // Virtual links if not found "compile-time", we will try again to reprocess them on Meteor.startup
                // if a removal happens before Meteor.startup this may fail
                if (relatedLinker) {
                    let link = relatedLinker.createLink(linkedObj);

                    if (relatedLinker.isSingle()) {
                        link.unset();
                    } else {
                        link.remove(doc);
                    }
                }
            });
        })
    }

    _initIndex() {
        if (Meteor.isServer) {
            let field = this.linkConfig.field;
            if (this.linkConfig.metadata) {
                field = field + '._id';
            }

            if (this.linkConfig.index) {
                if (this.isVirtual()) {
                    throw new Meteor.Error('You cannot set index on an inversed link.');
                }

                let options;
                if (this.linkConfig.unique) {
                    if (this.isMany()) {
                        throw new Meteor.Error('You cannot set unique property on a multi field.');
                    }

                    options = {unique: true}
                }

                this.mainCollection._ensureIndex({[field]: 1}, options);
            } else {
                if (this.linkConfig.unique) {
                    if (this.isVirtual()) {
                        throw new Meteor.Error('You cannot set unique property on an inversed link.');
                    }

                    if (this.isMany()) {
                        throw new Meteor.Error('You cannot set unique property on a multi field.');
                    }

                    this.mainCollection._ensureIndex({
                        [field]: 1
                    }, {unique: true})
                }
            }
        }
    }

    _initAutoremove() {
        if (!this.isVirtual()) {
            this.mainCollection.after.remove((userId, doc) => {
                this.getLinkedCollection().remove({
                    _id: {
                        $in: smartArguments.getIds(doc[this.linkStorageField])
                    }
                })
            })
        } else {
            this.mainCollection.after.remove((userId, doc) => {
                const linker = this.mainCollection.getLink(doc, this.linkName);
                const ids = linker.find({}, {fields: {_id: 1}}).fetch().map(item => item._id);

                this.getLinkedCollection().remove({
                    _id: {$in: ids}
                })
            })
        }
    }
}<commit_msg>When you have already defined a schema on a collection and you refence this in links, there's no need to extend the schema. Just use the current schema<commit_after>import LinkMany from './linkTypes/linkMany.js';
import LinkManyMeta from './linkTypes/linkManyMeta.js';
import LinkOne from './linkTypes/linkOne.js';
import LinkOneMeta from './linkTypes/linkOneMeta.js';
import LinkResolve from './linkTypes/linkResolve.js';
import {linkStorage as linkStorageSymbol} from './symbols.js';
import ConfigSchema from './config.schema.js';
import createFieldSchema from './lib/createFieldSchema.js';
import smartArguments from './linkTypes/lib/smartArguments';

export default class Linker {
    /**
     * @param mainCollection
     * @param linkName
     * @param linkConfig
     */
    constructor(mainCollection, linkName, linkConfig) {
        this.mainCollection = mainCollection;
        this.linkConfig = linkConfig;
        this.linkName = linkName;

        // check linkName must not exist in schema
        this._validateAndClean();
        this._extendSchema();

        // initialize cascade removal hooks.
        if (linkConfig.autoremove) {
            this._initAutoremove();
        }

        if (this.isVirtual()) {
            // if it's a virtual field make sure that when this is deleted, it will be removed from the references
            if (!linkConfig.autoremove) {
                this._handleReferenceRemovalForVirtualLinks();
            }
        } else {
            this._initIndex();
        }
    }

    /**
     * Values which represent for the relation a single link
     * @returns {string[]}
     */
    get oneTypes() {
        return ['one', '1', 'single'];
    }

    /**
     * Returns the strategies: one, many, one-meta, many-meta
     * @returns {string}
     */
    get strategy() {
        if (this.isResolver()) {
            return 'resolver';
        }

        let strategy = this.isMany() ? 'many' : 'one';
        if (this.linkConfig.metadata) {
            strategy += '-meta';
        }

        return strategy;
    }

    /**
     * Returns the field name in the document where the actual relationships are stored.
     * @returns string
     */
    get linkStorageField() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.linkStorageField;
        }

        return this.linkConfig.field;
    }

    /**
     * The collection that is linked with the current collection
     * @returns Mongo.Collection
     */
    getLinkedCollection() {
        // if our link is a resolver, then we really don't have a linked collection.
        if (this.isResolver()) {
            return null;
        }

        return this.linkConfig.collection;
    }

    /**
     * If the relationship for this link is of "many" type.
     */
    isMany() {
        return !this.isSingle();
    }

    /**
     * If the relationship for this link contains metadata
     */
    isMeta() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.isMeta();
        }

        return !!this.linkConfig.metadata;
    }

    /**
     * @returns {boolean}
     */
    isSingle() {
        if (this.isVirtual()) {
            return this.linkConfig.relatedLinker.isSingle();
        }

        return _.contains(this.oneTypes, this.linkConfig.type);
    }

    /**
     * @returns {boolean}
     */
    isVirtual() {
        return !!this.linkConfig.inversedBy;
    }

    /**
     * @returns {boolean}
     */
    isResolver() {
        return _.isFunction(this.linkConfig.resolve);
    }

    /**
     * Should return a single result.
     */
    isOneResult() {
        return (
            (this.isVirtual() && this.linkConfig.relatedLinker.linkConfig.unique)
            || (!this.isVirtual() && this.isSingle())
        );
    }

    /**
     * @param object
     * @param collection To impersonate the getLinkedCollection() of the "Linker"
     *
     * @returns {LinkOne|LinkMany|LinkManyMeta|LinkOneMeta|LinkResolve}
     */
    createLink(object, collection = null) {
        let helperClass = this._getHelperClass();

        return new helperClass(this, object, collection);
    }

    /**
     * @returns {*}
     * @private
     */
    _validateAndClean() {
        if (!this.isResolver()) {
            if (!this.linkConfig.collection) {
                throw new Meteor.Error('invalid-config', `For the link ${this.linkName} you did not provide a collection. Collection is mandatory for non-resolver links.`)
            }

            if (typeof(this.linkConfig.collection) === 'string') {
                const collectionName = this.linkConfig.collection;
                this.linkConfig.collection = Mongo.Collection.get(collectionName);

                if (!this.linkConfig.collection) {
                    throw new Meteor.Error('invalid-collection', `Could not find a collection with the name: ${collectionName}`);
                }
            }

            if (this.isVirtual()) {
                return this._prepareVirtual();
            } else {
                if (!this.linkConfig.type) {
                    this.linkConfig.type = 'one';
                }

                if (!this.linkConfig.field) {
                    this.linkConfig.field = this._generateFieldName();
                } else {
                    if (this.linkConfig.field == this.linkName) {
                        throw new Meteor.Error('invalid-config', `For the link ${this.linkName} you must not use the same name for the field, otherwise it will cause conflicts when fetching data`);
                    }
                }
            }
        }

        ConfigSchema.validate(this.linkConfig);
    }

    /**
     * We need to apply same type of rules in this case.
     * @private
     */
    _prepareVirtual() {
        const {collection, inversedBy} = this.linkConfig;
        let linker = collection.getLinker(inversedBy);

        if (!linker) {
            // it is possible that the collection doesn't have a linker created yet.
            // so we will create it on startup after all links have been defined
            Meteor.startup(() => {
                linker = collection.getLinker(inversedBy);
                if (!linker) {
                    throw new Meteor.Error(`You tried setting up an inversed link "${inversedBy}" in "${this.mainCollection._name}" to the "${collection._name}" collection that does not have any links defined`)
                } else {
                    this._setupVirtualConfig(linker);
                }
            })
        } else {
            this._setupVirtualConfig(linker);
        }
    }

    /**
     * @param linker
     * @private
     */
    _setupVirtualConfig(linker) {
        const virtualLinkConfig = linker.linkConfig;

        if (!virtualLinkConfig) {
            throw new Meteor.Error(`There is no link-config for the related collection on ${inversedBy}. Make sure you added the direct links before specifying virtual ones.`)
        }

        _.extend(this.linkConfig, {
            metadata: virtualLinkConfig.metadata,
            relatedLinker: linker
        });
    }

    /**
     * Depending on the strategy, we create the proper helper class
     * @private
     */
    _getHelperClass() {
        switch (this.strategy) {
            case 'resolver':
                return LinkResolve;
            case 'many-meta':
                return LinkManyMeta;
            case 'many':
                return LinkMany;
            case 'one-meta':
                return LinkOneMeta;
            case 'one':
                return LinkOne;
        }

        throw new Meteor.Error('invalid-strategy', `${this.strategy} is not a valid strategy`);
    }

    /**
     * Extends the schema of the collection.
     * @private
     */
    _extendSchema() {
        if (this.isVirtual() || this.isResolver()) {
            return;
        }

        if (this.mainCollection.simpleSchema && this.mainCollection.simpleSchema()) {
            const schema = createFieldSchema(this.isMany(), this.linkConfig.metadata);

            if(! this.mainCollection.simpleSchema()._schemaKeys.includes(this.linkConfig.field)) {
              this.mainCollection.attachSchema({
                  [this.linkConfig.field]: schema
              });
            }
        }
    }

    /**
     * If field name not present, we generate it.
     * @private
     */
    _generateFieldName() {
        let cleanedCollectionName = this.linkConfig.collection._name.replace(/\./g, '_');
        let defaultFieldPrefix = this.linkName + '_' + cleanedCollectionName;

        switch (this.strategy) {
            case 'many-meta':
                return `${defaultFieldPrefix}_metas`;
            case 'many':
                return `${defaultFieldPrefix}_ids`;
            case 'one-meta':
                return `${defaultFieldPrefix}_meta`;
            case 'one':
                return `${defaultFieldPrefix}_id`;
        }
    }

    /**
     * When a link that is declared virtual is removed, the reference will be removed from every other link.
     * @private
     */
    _handleReferenceRemovalForVirtualLinks() {
        this.mainCollection.after.remove((userId, doc) => {
            let accessor = this.createLink(doc);

            _.each(accessor.fetch(), linkedObj => {
                const {relatedLinker} = this.linkConfig;
                // We do this check, to avoid self-referencing hell when defining virtual links
                // Virtual links if not found "compile-time", we will try again to reprocess them on Meteor.startup
                // if a removal happens before Meteor.startup this may fail
                if (relatedLinker) {
                    let link = relatedLinker.createLink(linkedObj);

                    if (relatedLinker.isSingle()) {
                        link.unset();
                    } else {
                        link.remove(doc);
                    }
                }
            });
        })
    }

    _initIndex() {
        if (Meteor.isServer) {
            let field = this.linkConfig.field;
            if (this.linkConfig.metadata) {
                field = field + '._id';
            }

            if (this.linkConfig.index) {
                if (this.isVirtual()) {
                    throw new Meteor.Error('You cannot set index on an inversed link.');
                }

                let options;
                if (this.linkConfig.unique) {
                    if (this.isMany()) {
                        throw new Meteor.Error('You cannot set unique property on a multi field.');
                    }

                    options = {unique: true}
                }

                this.mainCollection._ensureIndex({[field]: 1}, options);
            } else {
                if (this.linkConfig.unique) {
                    if (this.isVirtual()) {
                        throw new Meteor.Error('You cannot set unique property on an inversed link.');
                    }

                    if (this.isMany()) {
                        throw new Meteor.Error('You cannot set unique property on a multi field.');
                    }

                    this.mainCollection._ensureIndex({
                        [field]: 1
                    }, {unique: true})
                }
            }
        }
    }

    _initAutoremove() {
        if (!this.isVirtual()) {
            this.mainCollection.after.remove((userId, doc) => {
                this.getLinkedCollection().remove({
                    _id: {
                        $in: smartArguments.getIds(doc[this.linkStorageField])
                    }
                })
            })
        } else {
            this.mainCollection.after.remove((userId, doc) => {
                const linker = this.mainCollection.getLink(doc, this.linkName);
                const ids = linker.find({}, {fields: {_id: 1}}).fetch().map(item => item._id);

                this.getLinkedCollection().remove({
                    _id: {$in: ids}
                })
            })
        }
    }
}
<|endoftext|>


<commit_before>/* Check for valid JSON
 */

// Checks whether the keys in objects are in sorted order.
function isCanonicalObj(obj) {
    if (Array.isArray(obj)) {
        return obj.every(isCanonicalObj);
    } else if (typeof obj === 'object' && obj !== null) {
        var previousKey;
        for (var key in obj) {
            if (typeof previousKey !== 'undefined' && key < previousKey || !isCanonicalObj(obj[key])) {
                return false;
            }
            previousKey = key;
        }
    }
    return true;
}

module.exports = function (filename, body, config, callback) {
    var results = [],
        parsedObj;

    config = config || {};

    try {
        parsedObj = JSON.parse(body);
    } catch (e) {
        results.push({
            filename: filename,
            line: 0,
            character: 0,
            message: 'JSON does not parse.'
        });
    }

    if (typeof parsedObj !== 'undefined') {
        if ('canonical' in config && config.canonical && !isCanonicalObj(parsedObj)) {
            results.push({
                filename: filename,
                // We'll need a different JSON parser to get to this info:
                line: 0,
                character: 0,
                message: 'JSON is not canonical'
            });
        }
        if ('indent' in config) {
            var reserializedJson = JSON.stringify(parsedObj, undefined, config.indent);

            // Tolerate differences in trailing new lines and carriage returns when comparing to the expected output
            // (that is better handled by the trailing-spaces linter):
            if (reserializedJson.replace(/[\r\n]+$/, '') !== body.replace(/[\r\n]+$/, '')) {
                results.push({
                    filename: filename,
                    // We'll need a different JSON parser to get to this info:
                    line: 0,
                    character: 0,
                    message: 'JSON is not indented correctly'
                });
            }
        }
    }

    callback(undefined, results);
};
<commit_msg>Lint json.js<commit_after>/* Check for valid JSON
 */

// Checks whether the keys in objects are in sorted order.
function isCanonicalObj(obj) {
    if (Array.isArray(obj)) {
        return obj.every(isCanonicalObj);
    } else if (typeof obj === 'object' && obj !== null) {
        var previousKey;
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (typeof previousKey !== 'undefined' && key < previousKey || !isCanonicalObj(obj[key])) {
                    return false;
                }
                previousKey = key;
            }
        }
    }
    return true;
}

module.exports = function (filename, body, config, callback) {
    var results = [],
        parsedObj;

    config = config || {};

    try {
        parsedObj = JSON.parse(body);
    } catch (e) {
        results.push({
            filename: filename,
            line: 0,
            character: 0,
            message: 'JSON does not parse.'
        });
    }

    if (typeof parsedObj !== 'undefined') {
        if ('canonical' in config && config.canonical && !isCanonicalObj(parsedObj)) {
            results.push({
                filename: filename,
                // We'll need a different JSON parser to get to this info:
                line: 0,
                character: 0,
                message: 'JSON is not canonical'
            });
        }
        if ('indent' in config) {
            var reserializedJson = JSON.stringify(parsedObj, undefined, config.indent);

            // Tolerate differences in trailing new lines and carriage returns when comparing to the expected output
            // (that is better handled by the trailing-spaces linter):
            if (reserializedJson.replace(/[\r\n]+$/, '') !== body.replace(/[\r\n]+$/, '')) {
                results.push({
                    filename: filename,
                    // We'll need a different JSON parser to get to this info:
                    line: 0,
                    character: 0,
                    message: 'JSON is not indented correctly'
                });
            }
        }
    }

    callback(undefined, results);
};
<|endoftext|>


<commit_before>// Load in dependencies
var http = require('http'),
    EventEmitter = require('events').EventEmitter,
    SingleChild = require('single-child');

// Export a module to spawn servers
function ListenSpawn(cmd, args, options) {
  // If there are arguments, fallback options
  if (Array.isArray(args)) {
    options = options || {};
  } else {
  // Otherwise, fallback args
    options = args || {};
    args = [];
  }

  // Call the EventEmitter constructor
  EventEmitter.call(this);

  // Generate a child singleton
  var child = new SingleChild(cmd, args, options);
  this.child = child;

  // When the child kill starts, log it
  var that = this;
  child.on('killing', function () {
    that.emit('killing');
  });

  // When the child start starts, log it
  child.on('starting', function () {
    that.emit('starting');
  });

  // When the child exited starts, log it
  child.on('exited', function (code, signal) {
    that.emit('exited', code, signal);
  });

  // Start a new server
  var server = http.createServer(function startListenSpawnServer (req, res) {
        // Notify that a request has been received
        that.emit('request');

        // Restart the child
        that.restartChild();

        // Close the connection with a 204
        res.writeHead(204);
        res.end();
      });

  // Begin listening
  var port = options.port || 7060;
  server.listen(port);

  // Notify the server is listening
  this.emit('listen');

  // Start the child
  this.restartChild();
}
var ListenSpawnProto = {
  restartChild: function (cb) {
    // Save `that` for later
    var that = this;

    // If the child is restarting
    if (this._restartingChild) {
      // When the child is completed starting, callback
      this.child.once('started', function () {
        if (cb) {
          return cb.apply(this, arguments);
        }
      });

      // Emit an ignore event
      this.emit('restart-ignored');
    } else {
    // Otherwise...
      // Mark the child as restarting
      this._restartingChild = true;

      // Restart the child
      this.child.restart(function childRestarted (err, child) {
        // Mark the child as done restarting
        that._restartingChild = false;

        // Callback with the child
        if (cb) {
          cb(err, child);
        }
      });
    }
  }
};
ListenSpawn.prototype = ListenSpawnProto;

// Duck-punch EventEmitter methods
var EventProto = EventEmitter.prototype,
    key;
for (key in EventProto) {
  ListenSpawnProto[key] = EventProto[key];
}

// Export ListenSpawn
module.exports = ListenSpawn;<commit_msg>Cleaning up event emissions<commit_after>// Load in dependencies
var http = require('http'),
    EventEmitter = require('events').EventEmitter,
    SingleChild = require('single-child');

// Export a module to spawn servers
function ListenSpawn(cmd, args, options) {
  // If there are arguments, fallback options
  if (Array.isArray(args)) {
    options = options || {};
  } else {
  // Otherwise, fallback args
    options = args || {};
    args = [];
  }

  // Call the EventEmitter constructor
  EventEmitter.call(this);

  // Generate a child singleton
  var child = new SingleChild(cmd, args, options);
  this.child = child;

  // Forward a bunch of events
  // ANTI-PATTERN: Losing stack trace for each item by using same line
  var that = this;
  ['killing', 'killed', 'starting', 'started', 'exited'].forEach(function listenEvent (event) {
    // When the event occurs
    child.on(event, function forwardEvent () {
      // Add the event name to our arguments
      var args = [].slice.call(arguments);
      args.unshift(event);

      // Emit our event
      return that.emit.apply(that, args);
    });
  });

  // Start a new server
  var server = http.createServer(function startListenSpawnServer (req, res) {
        // Notify that a request has been received
        that.emit('request');

        // Restart the child
        that.restartChild();

        // Close the connection with a 204
        res.writeHead(204);
        res.end();
      });

  // Begin listening
  var port = options.port || 7060;
  server.listen(port);

  // Notify the server is listening
  this.emit('listen');

  // Start the child
  this.restartChild();
}
var ListenSpawnProto = {
  restartChild: function (cb) {
    // Save `that` for later
    var that = this;

    // If the child is restarting
    if (this._restartingChild) {
      // When the child is completed starting, callback
      this.child.once('started', function () {
        if (cb) {
          return cb.apply(this, arguments);
        }
      });

      // Emit an ignore event
      this.emit('restart-ignored');
    } else {
    // Otherwise...
      // Mark the child as restarting
      this._restartingChild = true;

      // Restart the child
      this.child.restart(function childRestarted (err, child) {
        // Mark the child as done restarting
        that._restartingChild = false;

        // Callback with the child
        if (cb) {
          cb(err, child);
        }
      });
    }
  }
};
ListenSpawn.prototype = ListenSpawnProto;

// Duck-punch EventEmitter methods
var EventProto = EventEmitter.prototype,
    key;
for (key in EventProto) {
  ListenSpawnProto[key] = EventProto[key];
}

// Export ListenSpawn
module.exports = ListenSpawn;<|endoftext|>


<commit_before>/**
 * Load Watcher
 * Belongs to Decentraleyes.
 *
 * @author      Thomas Rientjes
 * @since       2016-02-04
 * @license     MPL 2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

/**
 * Imports
 */

var { Class } = require('sdk/core/heritage');
var { Unknown, Factory } = require('sdk/platform/xpcom');
var { Cc, Ci, Cu } = require('chrome');

var xpcom = require('sdk/platform/xpcom');

/**
 * Resource version mappings.
 * @var {object} mappings
 */
var mappings = require('./mappings');

/**
 * Retains data across application restarts.
 * @var {object} simpleStorage
 */
var simpleStorage = require('sdk/simple-storage');

//noinspection JSUnresolvedFunction
var categoryManager = Cc['@mozilla.org/categorymanager;1']
    .getService(Ci.nsICategoryManager);

/**
 * Constants
 */

const CONTRACT_ID = '@decentraleyes.org/load-watcher;1';
const SCRIPT_CONTENT_TYPE = Ci.nsIContentPolicy.TYPE_SCRIPT;
const SCRIPT_ELEMENT = Ci.nsIDOMHTMLScriptElement;
const REQUEST_ACCEPTATION = Ci.nsIContentPolicy.ACCEPT;

/**
 * Variables
 */

var storage = simpleStorage.storage;

/**
 * Tainted domains that are not automatically detectable.
 * @var {object} undetectableTaintedDomains
 */
var undetectableTaintedDomains = {

    'api-maps.yandex.ru', true,
    'direct.yandex.com': true,
    'direct.yandex.ru': true,
    'disk.yandex.com': true,
    'disk.yandex.ru': true,
    'mail.yandex.com': true,
    'mail.yandex.ru': true,
    'minigames.mail.ru': true,
    'news.yandex.ru': true,
    'news.yandex.ua': true,
    'passport.twitch.tv': true,
    'ya.ru': true,
    'yandex.com': true,
    'yandex.ru': true
};

/**
 * Initializations
 */

storage.taintedDomains = storage.taintedDomains || undetectableTaintedDomains;

/**
 * Load Watcher Class
 */

var LoadWatcher = new Class({

    extends: Unknown,
    interfaces: ['nsIContentPolicy'],
    get wrappedJSObject() {
        return this
    },

    register: function () {

        categoryManager.deleteCategoryEntry('content-policy', CONTRACT_ID, false);
        categoryManager.addCategoryEntry('content-policy', CONTRACT_ID, CONTRACT_ID, false, true);
    },

    shouldLoad: function (contentType, contentLocation, requestOrigin, node) {

        if (contentType == SCRIPT_CONTENT_TYPE && mappings[contentLocation.host]) {

            if (node instanceof SCRIPT_ELEMENT) {

                if (node.hasAttribute('crossorigin') || node.hasAttribute('integrity')) {

                    // Add corresponding origin domain to the list of tainted domains.
                    storage.taintedDomains[requestOrigin.host] = true;
                }
            }
        }

        // Accept the resource load request.
        return REQUEST_ACCEPTATION;
    }
});

/**
 * Load Watcher Factory
 */

var factory = Factory({

    contract: CONTRACT_ID,
    Component: LoadWatcher,
    unregister: false
});

/**
 * Unregister
 */

var unload = require('sdk/system/unload');

unload.when(function () {

    function trueUnregister() {

        categoryManager.deleteCategoryEntry('content-policy', CONTRACT_ID, false);

        try {
            xpcom.unregister(factory);
        } catch (exception) {
            Cu.reportError(exception);
        }
    }

    if ('dispatch' in Cu) {
        Cu.dispatch(trueUnregister, trueUnregister);
    } else {
        Cu.import('resource://gre/modules/Services.jsm');
        Services.tm.mainThread.dispatch(trueUnregister, 0);
    }
});

/**
 * Exports
 */

module.exports = LoadWatcher;
<commit_msg>Correct undetectable tainted domains<commit_after>/**
 * Load Watcher
 * Belongs to Decentraleyes.
 *
 * @author      Thomas Rientjes
 * @since       2016-02-04
 * @license     MPL 2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

/**
 * Imports
 */

var { Class } = require('sdk/core/heritage');
var { Unknown, Factory } = require('sdk/platform/xpcom');
var { Cc, Ci, Cu } = require('chrome');

var xpcom = require('sdk/platform/xpcom');

/**
 * Resource version mappings.
 * @var {object} mappings
 */
var mappings = require('./mappings');

/**
 * Retains data across application restarts.
 * @var {object} simpleStorage
 */
var simpleStorage = require('sdk/simple-storage');

//noinspection JSUnresolvedFunction
var categoryManager = Cc['@mozilla.org/categorymanager;1']
    .getService(Ci.nsICategoryManager);

/**
 * Constants
 */

const CONTRACT_ID = '@decentraleyes.org/load-watcher;1';
const SCRIPT_CONTENT_TYPE = Ci.nsIContentPolicy.TYPE_SCRIPT;
const SCRIPT_ELEMENT = Ci.nsIDOMHTMLScriptElement;
const REQUEST_ACCEPTATION = Ci.nsIContentPolicy.ACCEPT;

/**
 * Variables
 */

var storage = simpleStorage.storage;

/**
 * Tainted domains that are not automatically detectable.
 * @var {object} undetectableTaintedDomains
 */
var undetectableTaintedDomains = {

    'api-maps.yandex.ru': true,
    'direct.yandex.com': true,
    'direct.yandex.ru': true,
    'disk.yandex.com': true,
    'disk.yandex.ru': true,
    'mail.yandex.com': true,
    'mail.yandex.ru': true,
    'minigames.mail.ru': true,
    'news.yandex.ru': true,
    'news.yandex.ua': true,
    'passport.twitch.tv': true,
    'ya.ru': true,
    'yandex.com': true,
    'yandex.ru': true
};

/**
 * Initializations
 */

storage.taintedDomains = storage.taintedDomains || undetectableTaintedDomains;

/**
 * Load Watcher Class
 */

var LoadWatcher = new Class({

    extends: Unknown,
    interfaces: ['nsIContentPolicy'],
    get wrappedJSObject() {
        return this
    },

    register: function () {

        categoryManager.deleteCategoryEntry('content-policy', CONTRACT_ID, false);
        categoryManager.addCategoryEntry('content-policy', CONTRACT_ID, CONTRACT_ID, false, true);
    },

    shouldLoad: function (contentType, contentLocation, requestOrigin, node) {

        if (contentType == SCRIPT_CONTENT_TYPE && mappings[contentLocation.host]) {

            if (node instanceof SCRIPT_ELEMENT) {

                if (node.hasAttribute('crossorigin') || node.hasAttribute('integrity')) {

                    // Add corresponding origin domain to the list of tainted domains.
                    storage.taintedDomains[requestOrigin.host] = true;
                }
            }
        }

        // Accept the resource load request.
        return REQUEST_ACCEPTATION;
    }
});

/**
 * Load Watcher Factory
 */

var factory = Factory({

    contract: CONTRACT_ID,
    Component: LoadWatcher,
    unregister: false
});

/**
 * Unregister
 */

var unload = require('sdk/system/unload');

unload.when(function () {

    function trueUnregister() {

        categoryManager.deleteCategoryEntry('content-policy', CONTRACT_ID, false);

        try {
            xpcom.unregister(factory);
        } catch (exception) {
            Cu.reportError(exception);
        }
    }

    if ('dispatch' in Cu) {
        Cu.dispatch(trueUnregister, trueUnregister);
    } else {
        Cu.import('resource://gre/modules/Services.jsm');
        Services.tm.mainThread.dispatch(trueUnregister, 0);
    }
});

/**
 * Exports
 */

module.exports = LoadWatcher;
<|endoftext|>


<commit_before>import DataLoader from "dataloader"
import gravity from "lib/apis/gravity"
import impulse from "lib/apis/impulse"
import positron from "lib/apis/positron"
import { toKey } from "lib/helpers"

import timer from "lib/timer"
import { verbose, error } from "lib/loggers"
import { pick } from "lodash"

// FIXME This legacy loader needs to be removed.
import cachingHttpLoader from "./legacy/http"

const loaderInterface = (loader, path, globalParams) => (id, params = {}) => {
  const key = toKey(typeof path === "function" ? path(id) : path, Object.assign({}, globalParams, params))
  return loader.load(key)
}

// TODO As we’re currently re-using the http loader as it was, it won’t support batching yet.
//      We should rework that once we’ve changed all resolve functions to only use per-type loaders.
const loaderWithoutAuthentication = api => {
  return (path, globalParams = {}) => {
    const loader = cachingHttpLoader(api)
    return loaderInterface(loader, path, globalParams)
  }
}

const loaderWithAuthentication = api => {
  return accessTokenLoader => {
    return (path, apiOptions = {}, globalParams = {}) => {
      const loader = new DataLoader(
        keys => {
          return accessTokenLoader().then(accessToken =>
            Promise.all(
              keys.map(key => {
                const clock = timer(path)
                clock.start()
                return new Promise((resolve, reject) => {
                  verbose(`Requested: ${key}`)
                  api(key, accessToken, apiOptions)
                    .then(response => {
                      if (apiOptions.headers) {
                        resolve(pick(response, ["body", "headers"]))
                      } else {
                        resolve(response.body)
                      }
                      clock.end()
                    })
                    .catch(err => {
                      error(path, err)
                      reject(err)
                    })
                })
              })
            )
          )
        },
        {
          batch: true,
          cache: true,
        }
      )
      return loaderInterface(loader, path, globalParams)
    }
  }
}

/**
 * This loader caches all responses to memcache. Do NOT use this for authenticated requests!
 */
export const gravityLoaderWithoutAuthentication = loaderWithoutAuthentication(gravity)
export const positronLoaderWithoutAuthentication = loaderWithoutAuthentication(positron)

/**
 * This loader does cache responses for the duration of query execution but does not cache to memcache. Use this for
 * authenticated requests.
 */
export const gravityLoaderWithAuthentication = loaderWithAuthentication(gravity)
export const impulseLoaderWithAuthentication = loaderWithAuthentication(impulse)
<commit_msg>[loaders] Include params in logging of authenticated loaders.<commit_after>import DataLoader from "dataloader"
import gravity from "lib/apis/gravity"
import impulse from "lib/apis/impulse"
import positron from "lib/apis/positron"
import { toKey } from "lib/helpers"

import timer from "lib/timer"
import { verbose, error } from "lib/loggers"
import { pick } from "lodash"

// FIXME This legacy loader needs to be removed.
import cachingHttpLoader from "./legacy/http"

const loaderInterface = (loader, path, globalParams) => (id, params = {}) => {
  const key = toKey(typeof path === "function" ? path(id) : path, Object.assign({}, globalParams, params))
  return loader.load(key)
}

// TODO As we’re currently re-using the http loader as it was, it won’t support batching yet.
//      We should rework that once we’ve changed all resolve functions to only use per-type loaders.
const loaderWithoutAuthentication = api => {
  return (path, globalParams = {}) => {
    const loader = cachingHttpLoader(api)
    return loaderInterface(loader, path, globalParams)
  }
}

const loaderWithAuthentication = api => {
  return accessTokenLoader => {
    return (path, apiOptions = {}, globalParams = {}) => {
      const loader = new DataLoader(
        keys => {
          return accessTokenLoader().then(accessToken =>
            Promise.all(
              keys.map(key => {
                const clock = timer(key)
                clock.start()
                return new Promise((resolve, reject) => {
                  verbose(`Requested: ${key}`)
                  api(key, accessToken, apiOptions)
                    .then(response => {
                      if (apiOptions.headers) {
                        resolve(pick(response, ["body", "headers"]))
                      } else {
                        resolve(response.body)
                      }
                      clock.end()
                    })
                    .catch(err => {
                      error(path, err)
                      reject(err)
                    })
                })
              })
            )
          )
        },
        {
          batch: true,
          cache: true,
        }
      )
      return loaderInterface(loader, path, globalParams)
    }
  }
}

/**
 * This loader caches all responses to memcache. Do NOT use this for authenticated requests!
 */
export const gravityLoaderWithoutAuthentication = loaderWithoutAuthentication(gravity)
export const positronLoaderWithoutAuthentication = loaderWithoutAuthentication(positron)

/**
 * This loader does cache responses for the duration of query execution but does not cache to memcache. Use this for
 * authenticated requests.
 */
export const gravityLoaderWithAuthentication = loaderWithAuthentication(gravity)
export const impulseLoaderWithAuthentication = loaderWithAuthentication(impulse)
<|endoftext|>


<commit_before>var Cast = require('../util/cast');

var Scratch3LooksBlocks = function (runtime) {
    /**
     * The runtime instantiating this block package.
     * @type {Runtime}
     */
    this.runtime = runtime;
};

/**
 * Retrieve the block primitives implemented by this package.
 * @return {Object.<string, Function>} Mapping of opcode to Function.
 */
Scratch3LooksBlocks.prototype.getPrimitives = function () {
    return {
        looks_say: this.say,
        looks_sayforsecs: this.sayforsecs,
        looks_think: this.think,
        looks_thinkforsecs: this.sayforsecs,
        looks_show: this.show,
        looks_hide: this.hide,
        looks_switchcostumeto: this.switchCostume,
        looks_switchbackdropto: this.switchBackdrop,
        looks_switchbackdroptoandwait: this.switchBackdropAndWait,
        looks_nextcostume: this.nextCostume,
        looks_nextbackdrop: this.nextBackdrop,
        looks_changeeffectby: this.changeEffect,
        looks_seteffectto: this.setEffect,
        looks_cleargraphiceffects: this.clearEffects,
        looks_changesizeby: this.changeSize,
        looks_setsizeto: this.setSize,
        looks_gotofront: this.goToFront,
        looks_gobacklayers: this.goBackLayers,
        looks_size: this.getSize,
        looks_costumeorder: this.getCostumeIndex,
        looks_backdroporder: this.getBackdropIndex,
        looks_backdropname: this.getBackdropName
    };
};

Scratch3LooksBlocks.prototype.say = function (args, util) {
    util.target.setSay('say', args.MESSAGE);
};

Scratch3LooksBlocks.prototype.sayforsecs = function (args, util) {
    util.target.setSay('say', args.MESSAGE);
    return new Promise(function (resolve) {
        setTimeout(function () {
            // Clear say bubble and proceed.
            util.target.setSay();
            resolve();
        }, 1000 * args.SECS);
    });
};

Scratch3LooksBlocks.prototype.think = function (args, util) {
    util.target.setSay('think', args.MESSAGE);
};

Scratch3LooksBlocks.prototype.thinkforsecs = function (args, util) {
    util.target.setSay('think', args.MESSAGE);
    return new Promise(function (resolve) {
        setTimeout(function () {
            // Clear say bubble and proceed.
            util.target.setSay();
            resolve();
        }, 1000 * args.SECS);
    });
};

Scratch3LooksBlocks.prototype.show = function (args, util) {
    util.target.setVisible(true);
};

Scratch3LooksBlocks.prototype.hide = function (args, util) {
    util.target.setVisible(false);
};

/**
 * Utility function to set the costume or backdrop of a target.
 * Matches the behavior of Scratch 2.0 for different types of arguments.
 * @param {!Target} target Target to set costume/backdrop to.
 * @param {Any} requestedCostume Costume requested, e.g., 0, 'name', etc.
 * @param {boolean=} optZeroIndex Set to zero-index the requestedCostume.
 * @return {Array.<!Thread>} Any threads started by this switch.
 */
Scratch3LooksBlocks.prototype._setCostumeOrBackdrop = function (target,
        requestedCostume, optZeroIndex) {
    if (typeof requestedCostume === 'number') {
        target.setCostume(optZeroIndex ?
            requestedCostume : requestedCostume - 1);
    } else {
        var costumeIndex = target.getCostumeIndexByName(requestedCostume);
        if (costumeIndex > -1) {
            target.setCostume(costumeIndex);
        } else if (costumeIndex === 'previous costume' ||
                   costumeIndex === 'previous backdrop') {
            target.setCostume(target.currentCostume - 1);
        } else if (costumeIndex === 'next costume' ||
                   costumeIndex === 'next backdrop') {
            target.setCostume(target.currentCostume + 1);
        } else {
            var forcedNumber = Cast.toNumber(requestedCostume);
            if (!isNaN(forcedNumber)) {
                target.setCostume(optZeroIndex ?
                    forcedNumber : forcedNumber - 1);
            }
        }
    }
    if (target === this.runtime.getTargetForStage()) {
        // Target is the stage - start hats.
        var newName = target.sprite.costumes[target.currentCostume].name;
        return this.runtime.startHats('event_whenbackdropswitchesto', {
            BACKDROP: newName
        });
    }
    return [];
};

Scratch3LooksBlocks.prototype.switchCostume = function (args, util) {
    this._setCostumeOrBackdrop(util.target, args.COSTUME);
};

Scratch3LooksBlocks.prototype.nextCostume = function (args, util) {
    this._setCostumeOrBackdrop(
        util.target, util.target.currentCostume + 1, true
    );
};

Scratch3LooksBlocks.prototype.switchBackdrop = function (args) {
    this._setCostumeOrBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);
};

Scratch3LooksBlocks.prototype.switchBackdropAndWait = function (args, util) {
    // Have we run before, starting threads?
    if (!util.stackFrame.startedThreads) {
        // No - switch the backdrop.
        util.stackFrame.startedThreads = (
            this._setCostumeOrBackdrop(
                this.runtime.getTargetForStage(),
                args.BACKDROP
            )
        );
        if (util.stackFrame.startedThreads.length === 0) {
            // Nothing was started.
            return;
        }
    }
    // We've run before; check if the wait is still going on.
    var instance = this;
    var waiting = util.stackFrame.startedThreads.some(function (thread) {
        return instance.runtime.isActiveThread(thread);
    });
    if (waiting) {
        util.yield();
    }
};

Scratch3LooksBlocks.prototype.nextBackdrop = function () {
    var stage = this.runtime.getTargetForStage();
    this._setCostumeOrBackdrop(
        stage, stage.currentCostume + 1, true
    );
};

Scratch3LooksBlocks.prototype.changeEffect = function (args, util) {
    var effect = Cast.toString(args.EFFECT).toLowerCase();
    var change = Cast.toNumber(args.CHANGE);
    if (!util.target.effects.hasOwnProperty(effect)) return;
    var newValue = change + util.target.effects[effect];
    util.target.setEffect(effect, newValue);
};

Scratch3LooksBlocks.prototype.setEffect = function (args, util) {
    var effect = Cast.toString(args.EFFECT).toLowerCase();
    var value = Cast.toNumber(args.VALUE);
    util.target.setEffect(effect, value);
};

Scratch3LooksBlocks.prototype.clearEffects = function (args, util) {
    util.target.clearEffects();
};

Scratch3LooksBlocks.prototype.changeSize = function (args, util) {
    var change = Cast.toNumber(args.CHANGE);
    util.target.setSize(util.target.size + change);
};

Scratch3LooksBlocks.prototype.setSize = function (args, util) {
    var size = Cast.toNumber(args.SIZE);
    util.target.setSize(size);
};

Scratch3LooksBlocks.prototype.goToFront = function (args, util) {
    util.target.goToFront();
};

Scratch3LooksBlocks.prototype.goBackLayers = function (args, util) {
    util.target.goBackLayers(args.NUM);
};

Scratch3LooksBlocks.prototype.getSize = function (args, util) {
    return util.target.size;
};

Scratch3LooksBlocks.prototype.getBackdropIndex = function () {
    var stage = this.runtime.getTargetForStage();
    return stage.currentCostume + 1;
};

Scratch3LooksBlocks.prototype.getBackdropName = function () {
    var stage = this.runtime.getTargetForStage();
    return stage.sprite.costumes[stage.currentCostume].name;
};

Scratch3LooksBlocks.prototype.getCostumeIndex = function (args, util) {
    return util.target.currentCostume + 1;
};

module.exports = Scratch3LooksBlocks;
<commit_msg>Fix #327, "switch costume to [next/previous]" (#330)<commit_after>var Cast = require('../util/cast');

var Scratch3LooksBlocks = function (runtime) {
    /**
     * The runtime instantiating this block package.
     * @type {Runtime}
     */
    this.runtime = runtime;
};

/**
 * Retrieve the block primitives implemented by this package.
 * @return {Object.<string, Function>} Mapping of opcode to Function.
 */
Scratch3LooksBlocks.prototype.getPrimitives = function () {
    return {
        looks_say: this.say,
        looks_sayforsecs: this.sayforsecs,
        looks_think: this.think,
        looks_thinkforsecs: this.sayforsecs,
        looks_show: this.show,
        looks_hide: this.hide,
        looks_switchcostumeto: this.switchCostume,
        looks_switchbackdropto: this.switchBackdrop,
        looks_switchbackdroptoandwait: this.switchBackdropAndWait,
        looks_nextcostume: this.nextCostume,
        looks_nextbackdrop: this.nextBackdrop,
        looks_changeeffectby: this.changeEffect,
        looks_seteffectto: this.setEffect,
        looks_cleargraphiceffects: this.clearEffects,
        looks_changesizeby: this.changeSize,
        looks_setsizeto: this.setSize,
        looks_gotofront: this.goToFront,
        looks_gobacklayers: this.goBackLayers,
        looks_size: this.getSize,
        looks_costumeorder: this.getCostumeIndex,
        looks_backdroporder: this.getBackdropIndex,
        looks_backdropname: this.getBackdropName
    };
};

Scratch3LooksBlocks.prototype.say = function (args, util) {
    util.target.setSay('say', args.MESSAGE);
};

Scratch3LooksBlocks.prototype.sayforsecs = function (args, util) {
    util.target.setSay('say', args.MESSAGE);
    return new Promise(function (resolve) {
        setTimeout(function () {
            // Clear say bubble and proceed.
            util.target.setSay();
            resolve();
        }, 1000 * args.SECS);
    });
};

Scratch3LooksBlocks.prototype.think = function (args, util) {
    util.target.setSay('think', args.MESSAGE);
};

Scratch3LooksBlocks.prototype.thinkforsecs = function (args, util) {
    util.target.setSay('think', args.MESSAGE);
    return new Promise(function (resolve) {
        setTimeout(function () {
            // Clear say bubble and proceed.
            util.target.setSay();
            resolve();
        }, 1000 * args.SECS);
    });
};

Scratch3LooksBlocks.prototype.show = function (args, util) {
    util.target.setVisible(true);
};

Scratch3LooksBlocks.prototype.hide = function (args, util) {
    util.target.setVisible(false);
};

/**
 * Utility function to set the costume or backdrop of a target.
 * Matches the behavior of Scratch 2.0 for different types of arguments.
 * @param {!Target} target Target to set costume/backdrop to.
 * @param {Any} requestedCostume Costume requested, e.g., 0, 'name', etc.
 * @param {boolean=} optZeroIndex Set to zero-index the requestedCostume.
 * @return {Array.<!Thread>} Any threads started by this switch.
 */
Scratch3LooksBlocks.prototype._setCostumeOrBackdrop = function (target,
        requestedCostume, optZeroIndex) {
    if (typeof requestedCostume === 'number') {
        target.setCostume(optZeroIndex ?
            requestedCostume : requestedCostume - 1);
    } else {
        var costumeIndex = target.getCostumeIndexByName(requestedCostume);
        if (costumeIndex > -1) {
            target.setCostume(costumeIndex);
        } else if (requestedCostume === 'previous costume' ||
                   requestedCostume === 'previous backdrop') {
            target.setCostume(target.currentCostume - 1);
        } else if (requestedCostume === 'next costume' ||
                   requestedCostume === 'next backdrop') {
            target.setCostume(target.currentCostume + 1);
        } else {
            var forcedNumber = Cast.toNumber(requestedCostume);
            if (!isNaN(forcedNumber)) {
                target.setCostume(optZeroIndex ?
                    forcedNumber : forcedNumber - 1);
            }
        }
    }
    if (target === this.runtime.getTargetForStage()) {
        // Target is the stage - start hats.
        var newName = target.sprite.costumes[target.currentCostume].name;
        return this.runtime.startHats('event_whenbackdropswitchesto', {
            BACKDROP: newName
        });
    }
    return [];
};

Scratch3LooksBlocks.prototype.switchCostume = function (args, util) {
    this._setCostumeOrBackdrop(util.target, args.COSTUME);
};

Scratch3LooksBlocks.prototype.nextCostume = function (args, util) {
    this._setCostumeOrBackdrop(
        util.target, util.target.currentCostume + 1, true
    );
};

Scratch3LooksBlocks.prototype.switchBackdrop = function (args) {
    this._setCostumeOrBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);
};

Scratch3LooksBlocks.prototype.switchBackdropAndWait = function (args, util) {
    // Have we run before, starting threads?
    if (!util.stackFrame.startedThreads) {
        // No - switch the backdrop.
        util.stackFrame.startedThreads = (
            this._setCostumeOrBackdrop(
                this.runtime.getTargetForStage(),
                args.BACKDROP
            )
        );
        if (util.stackFrame.startedThreads.length === 0) {
            // Nothing was started.
            return;
        }
    }
    // We've run before; check if the wait is still going on.
    var instance = this;
    var waiting = util.stackFrame.startedThreads.some(function (thread) {
        return instance.runtime.isActiveThread(thread);
    });
    if (waiting) {
        util.yield();
    }
};

Scratch3LooksBlocks.prototype.nextBackdrop = function () {
    var stage = this.runtime.getTargetForStage();
    this._setCostumeOrBackdrop(
        stage, stage.currentCostume + 1, true
    );
};

Scratch3LooksBlocks.prototype.changeEffect = function (args, util) {
    var effect = Cast.toString(args.EFFECT).toLowerCase();
    var change = Cast.toNumber(args.CHANGE);
    if (!util.target.effects.hasOwnProperty(effect)) return;
    var newValue = change + util.target.effects[effect];
    util.target.setEffect(effect, newValue);
};

Scratch3LooksBlocks.prototype.setEffect = function (args, util) {
    var effect = Cast.toString(args.EFFECT).toLowerCase();
    var value = Cast.toNumber(args.VALUE);
    util.target.setEffect(effect, value);
};

Scratch3LooksBlocks.prototype.clearEffects = function (args, util) {
    util.target.clearEffects();
};

Scratch3LooksBlocks.prototype.changeSize = function (args, util) {
    var change = Cast.toNumber(args.CHANGE);
    util.target.setSize(util.target.size + change);
};

Scratch3LooksBlocks.prototype.setSize = function (args, util) {
    var size = Cast.toNumber(args.SIZE);
    util.target.setSize(size);
};

Scratch3LooksBlocks.prototype.goToFront = function (args, util) {
    util.target.goToFront();
};

Scratch3LooksBlocks.prototype.goBackLayers = function (args, util) {
    util.target.goBackLayers(args.NUM);
};

Scratch3LooksBlocks.prototype.getSize = function (args, util) {
    return util.target.size;
};

Scratch3LooksBlocks.prototype.getBackdropIndex = function () {
    var stage = this.runtime.getTargetForStage();
    return stage.currentCostume + 1;
};

Scratch3LooksBlocks.prototype.getBackdropName = function () {
    var stage = this.runtime.getTargetForStage();
    return stage.sprite.costumes[stage.currentCostume].name;
};

Scratch3LooksBlocks.prototype.getCostumeIndex = function (args, util) {
    return util.target.currentCostume + 1;
};

module.exports = Scratch3LooksBlocks;
<|endoftext|>


<commit_before>Ext.namespace('Gemma');

Gemma.MetaVisualizationPopups = {};

Gemma.MetaVisualizationPopups.makeGeneInfoWindow = function ( geneName, geneId ) {
	return new Ext.Window(
			{  width: 200,
			   height: 100,

				title: "Details for gene: "+geneName,
				layout:"vbox",
				items:[
				       {xtype:'label', ref:'aliases', text:"Aliases: "},
				       {xtype:'label', ref:'probes', text:"Probes: "},
				       {xtype:'label', ref:'geneGroups', text:"Gene groups: "},
				      ]
			});
};

Gemma.MetaVisualizationPopups.makeDatasetInfoWindow = function ( datasetName, datasetId ) {
	return new Ext.Window(
			{  width: 200,
			   height: 100,

				title: "Details for experession experiment: "+datasetName,
				layout:"vbox",
				items:[
				       {xtype:'label', ref:'description', text:"Description: "},
				       {xtype:'label', ref:'samples', text:"Samples: "},
				       {xtype:'label', ref:'tags', text:"Tags: "},
				       {xtype:'label', ref:'factors', text:"Factors: "},
				       // analyses?
				       // experimental design?
				      ]
			});
};
<commit_msg>Work-in-progress commit for diffexpr visualization. Font-end.<commit_after>Ext.namespace('Gemma');

Gemma.MetaVisualizationPopups = {};

Gemma.MetaVisualizationPopups.makeGeneInfoWindow = function ( geneName, geneId ) {
	return new Ext.Window(
			{  width: 200,
			   height: 100,

				title: "Details for gene: "+geneName,
				layout:"vbox",
				items:[
				       {xtype:'label', ref:'aliases', text:"Aliases: "},
				       {xtype:'label', ref:'probes', text:"Probes: "},
				       {xtype:'label', ref:'geneGroups', text:"Gene groups: "}
				      ]
			});
};

Gemma.MetaVisualizationPopups.makeDatasetInfoWindow = function ( datasetName, datasetId ) {
	return new Ext.Window(
			{  width: 200,
			   height: 100,

				title: "Details for experession experiment: "+datasetName,
				layout:"vbox",
				items:[
				       {xtype:'label', ref:'description', text:"Description: "},
				       {xtype:'label', ref:'samples', text:"Samples: "},
				       {xtype:'label', ref:'tags', text:"Tags: "},
				       {xtype:'label', ref:'factors', text:"Factors: "}
				       // analyses?
				       // experimental design?
				      ]
			});
};
<|endoftext|>