<commit_before>/* @flow */

import fastGlob from 'fast-glob'
import placeholders from 'placeholders'
import path from 'path'

import BuildState from './BuildState'
import File from './File'
import Rule from './Rule'

import type { Message } from './types'

export default class BuildStateConsumer {
  buildState: BuildState
  options: Object
  jobName: ?string
  expand: Function

  constructor (buildState: BuildState, jobName: ?string) {
    this.jobName = jobName
    this.buildState = buildState
    this.options = new Proxy(buildState.options, {
      get (target, key) {
        if (key === 'jobNames') {
          if ('jobName' in target) return [target.jobName]
          if ('jobNames' in target) return target.jobNames
          if ('jobs' in target) return Object.keys(target.jobs)
          return [undefined]
        } else if (jobName) {
          if (key === 'jobName') return jobName
          if (target.jobs) {
            const jobOptions = target.jobs[jobName]
            return (jobOptions && key in jobOptions) ? jobOptions[key] : target[key]
          }
        }
        return target[key]
      }
    })

    this.expand = placeholders({ regex: /:(\w+)/ })
  }

  get ruleClasses (): Array<Class<Rule>> {
    return this.buildState.ruleClasses
  }

  get filePath (): string {
    return this.buildState.filePath
  }

  get rootPath (): string {
    return this.buildState.rootPath
  }

  get files (): Iterator<File> {
    return this.buildState.files.values()
  }

  get rules (): Iterator<Rule> {
    return this.buildState.rules.values()
  }

  async addRule (rule: Rule): Promise<void> {
    await this.buildState.addRule(rule)
  }

  normalizePath (filePath: string) {
    return this.buildState.normalizePath(filePath)
  }

  resolvePath (filePath: string, reference?: File | string): string {
    const { dir, base, name, ext } = path.parse(reference instanceof File ? reference.normalizedFilePath : (reference || this.filePath))
    const properties = {
      outdir: this.options.outputDirectory || '.',
      outext: `.${this.options.outputFormat}`,
      job: this.jobName || this.options.jobName || name,
      dir: (reference instanceof File ? dir : this.rootPath) || '.',
      base,
      name,
      ext
    }
    return path.normalize(this.expand(filePath, properties))
  }

  async globPath (pattern: string, reference?: File | string): Promise<Array<string>> {
    return await fastGlob(this.resolvePath(pattern, reference), { cwd: this.rootPath })
  }

  async getFile (filePath: string): Promise<?File> {
    const file: ?File = await this.buildState.getFile(filePath)
    if (file && this.jobName) file.jobNames.add(this.jobName)
    return file
  }

  async getFiles (filePaths: Array<string>): Promise<Array<File>> {
    const files: Array<File> = []
    for (const filePath of filePaths) {
      const file = await this.getFile(filePath)
      if (file) files.push(file)
    }
    return files
  }

  async getGlobbedFiles (pattern: string, reference?: File | string): Promise<Array<File>> {
    const files = []
    for (const filePath of await this.globPath(pattern, reference)) {
      const file = await this.getFile(filePath)
      if (file) files.push(file)
    }
    return files
  }

  error (text: string) {
    this.log({ severity: 'error', text })
  }

  warning (text: string) {
    this.log({ severity: 'warning', text })
  }

  info (text: string) {
    this.log({ severity: 'info', text })
  }

  trace (text: string) {
    this.log({ severity: 'trace', text })
  }

  log (message: Message) {
    const severity = this.options.severity || 'warning'
    if ((severity === 'info' && message.severity === 'trace') ||
      (severity === 'warning' && (message.severity === 'trace' || message.severity === 'info')) ||
      (severity === 'error' && message.severity !== 'error')) return
    this.emit('log', { type: 'log', ...message })
  }

  calculateDistances (): void {
    this.buildState.calculateDistances()
  }

  getDistance (x: Rule, y: Rule): ?number {
    return this.buildState.getDistance(x, y)
  }

  isConnected (x: Rule, y: Rule): boolean {
    return this.buildState.isConnected(x, y)
  }

  isChild (x: Rule, y: Rule): boolean {
    return this.buildState.isChild(x, y)
  }

  async getResolvedFile (filePath: string, reference?: File | string): Promise<?File> {
    return await this.getFile(this.resolvePath(filePath, reference))
  }

  // EventEmmitter proxy
  addListener (eventName: string, listener: Function) {
    return this.buildState.addListener(eventName, listener)
  }

  emit (eventName: string, ...args: Array<any>) {
    return this.buildState.emit(eventName, ...args)
  }

  eventNames () {
    return this.buildState.eventNames()
  }

  getMaxListeners () {
    return this.buildState.eventNames()
  }

  listenerCount (eventName: string) {
    return this.buildState.listenerCount(eventName)
  }

  listeners (eventName: string) {
    return this.buildState.listeners(eventName)
  }

  on (eventName: string, listener: Function) {
    return this.buildState.on(eventName, listener)
  }

  once (eventName: string, listener: Function) {
    return this.buildState.once(eventName, listener)
  }

  prependListener (eventName: string, listener: Function) {
    return this.buildState.prependListener(eventName, listener)
  }

  prependOnceListener (eventName: string, listener: Function) {
    return this.buildState.prependOnceListener(eventName, listener)
  }

  removeAllListeners (eventName: string) {
    return this.buildState.removeAllListeners(eventName)
  }

  removeListener (eventName: string, listener: Function) {
    return this.buildState.removeListener(eventName, listener)
  }

  setMaxListeners (n: number) {
    return this.buildState.setMaxListeners(n)
  }
}
<commit_msg>Prevent native bash usage<commit_after>/* @flow */

import fastGlob from 'fast-glob'
import placeholders from 'placeholders'
import path from 'path'

import BuildState from './BuildState'
import File from './File'
import Rule from './Rule'

import type { Message } from './types'

export default class BuildStateConsumer {
  buildState: BuildState
  options: Object
  jobName: ?string
  expand: Function

  constructor (buildState: BuildState, jobName: ?string) {
    this.jobName = jobName
    this.buildState = buildState
    this.options = new Proxy(buildState.options, {
      get (target, key) {
        if (key === 'jobNames') {
          if ('jobName' in target) return [target.jobName]
          if ('jobNames' in target) return target.jobNames
          if ('jobs' in target) return Object.keys(target.jobs)
          return [undefined]
        } else if (jobName) {
          if (key === 'jobName') return jobName
          if (target.jobs) {
            const jobOptions = target.jobs[jobName]
            return (jobOptions && key in jobOptions) ? jobOptions[key] : target[key]
          }
        }
        return target[key]
      }
    })

    this.expand = placeholders({ regex: /:(\w+)/ })
  }

  get ruleClasses (): Array<Class<Rule>> {
    return this.buildState.ruleClasses
  }

  get filePath (): string {
    return this.buildState.filePath
  }

  get rootPath (): string {
    return this.buildState.rootPath
  }

  get files (): Iterator<File> {
    return this.buildState.files.values()
  }

  get rules (): Iterator<Rule> {
    return this.buildState.rules.values()
  }

  async addRule (rule: Rule): Promise<void> {
    await this.buildState.addRule(rule)
  }

  normalizePath (filePath: string) {
    return this.buildState.normalizePath(filePath)
  }

  resolvePath (filePath: string, reference?: File | string): string {
    const { dir, base, name, ext } = path.parse(reference instanceof File ? reference.normalizedFilePath : (reference || this.filePath))
    const properties = {
      outdir: this.options.outputDirectory || '.',
      outext: `.${this.options.outputFormat}`,
      job: this.jobName || this.options.jobName || name,
      dir: (reference instanceof File ? dir : this.rootPath) || '.',
      base,
      name,
      ext
    }
    return path.normalize(this.expand(filePath, properties))
  }

  async globPath (pattern: string, reference?: File | string): Promise<Array<string>> {
    return await fastGlob(this.resolvePath(pattern, reference), {
      cwd: this.rootPath,
      bashNative: []
    })
  }

  async getFile (filePath: string): Promise<?File> {
    const file: ?File = await this.buildState.getFile(filePath)
    if (file && this.jobName) file.jobNames.add(this.jobName)
    return file
  }

  async getFiles (filePaths: Array<string>): Promise<Array<File>> {
    const files: Array<File> = []
    for (const filePath of filePaths) {
      const file = await this.getFile(filePath)
      if (file) files.push(file)
    }
    return files
  }

  async getGlobbedFiles (pattern: string, reference?: File | string): Promise<Array<File>> {
    const files = []
    for (const filePath of await this.globPath(pattern, reference)) {
      const file = await this.getFile(filePath)
      if (file) files.push(file)
    }
    return files
  }

  error (text: string) {
    this.log({ severity: 'error', text })
  }

  warning (text: string) {
    this.log({ severity: 'warning', text })
  }

  info (text: string) {
    this.log({ severity: 'info', text })
  }

  trace (text: string) {
    this.log({ severity: 'trace', text })
  }

  log (message: Message) {
    const severity = this.options.severity || 'warning'
    if ((severity === 'info' && message.severity === 'trace') ||
      (severity === 'warning' && (message.severity === 'trace' || message.severity === 'info')) ||
      (severity === 'error' && message.severity !== 'error')) return
    this.emit('log', { type: 'log', ...message })
  }

  calculateDistances (): void {
    this.buildState.calculateDistances()
  }

  getDistance (x: Rule, y: Rule): ?number {
    return this.buildState.getDistance(x, y)
  }

  isConnected (x: Rule, y: Rule): boolean {
    return this.buildState.isConnected(x, y)
  }

  isChild (x: Rule, y: Rule): boolean {
    return this.buildState.isChild(x, y)
  }

  async getResolvedFile (filePath: string, reference?: File | string): Promise<?File> {
    return await this.getFile(this.resolvePath(filePath, reference))
  }

  // EventEmmitter proxy
  addListener (eventName: string, listener: Function) {
    return this.buildState.addListener(eventName, listener)
  }

  emit (eventName: string, ...args: Array<any>) {
    return this.buildState.emit(eventName, ...args)
  }

  eventNames () {
    return this.buildState.eventNames()
  }

  getMaxListeners () {
    return this.buildState.eventNames()
  }

  listenerCount (eventName: string) {
    return this.buildState.listenerCount(eventName)
  }

  listeners (eventName: string) {
    return this.buildState.listeners(eventName)
  }

  on (eventName: string, listener: Function) {
    return this.buildState.on(eventName, listener)
  }

  once (eventName: string, listener: Function) {
    return this.buildState.once(eventName, listener)
  }

  prependListener (eventName: string, listener: Function) {
    return this.buildState.prependListener(eventName, listener)
  }

  prependOnceListener (eventName: string, listener: Function) {
    return this.buildState.prependOnceListener(eventName, listener)
  }

  removeAllListeners (eventName: string) {
    return this.buildState.removeAllListeners(eventName)
  }

  removeListener (eventName: string, listener: Function) {
    return this.buildState.removeListener(eventName, listener)
  }

  setMaxListeners (n: number) {
    return this.buildState.setMaxListeners(n)
  }
}
<|endoftext|>


<commit_before>var fs = require('fs')
  , express = module.exports = require('express')
  , path = require('path')
  , hogan = require('hogan.js')
  , klass = global.klass = require('klass')
  , v = global.v = require('valentine')
  , router = require('./router')
  , argv = module.exports.argv = require('optimist').argv
  , minifyViews = process.env.minify || false
var minify = function () {
  var r = /(<script[^>]*>[\s\S]+?<\/script>)/
    , scr = /^<script([^>]*)>([\s\S]+?)<\/script>/
    , white = /\s+/g
    , closeTags = />\s+</g
    , jsp = require('uglify-js').parser
    , pro = require('uglify-js').uglify
    , uglify = function (src) {
        try {
          var ast = jsp.parse(src)
          ast = pro.ast_squeeze(ast)
          return pro.gen_code(ast)
        }
        catch (ex) {
          return src
        }
      }
  return function (doc) {
    if (!minifyViews) return doc
    return doc.trim().replace(/ +/g, ' ').split(r).map(function (p, i, m) {
      return (m = p.match(scr)) ? '<script' + m[1] + '>' + uglify(m[2]) + '</script>' : p.replace(white, ' ')
    }).join('').replace(closeTags, '><')
  }
}()

function isDirectory(p) {
  try {
    return fs.statSync(p).isDirectory()
  }
  catch (ex) {
    return false
  }
}

module.exports.createApp = function (baseDir, configuration, options) {
  configuration = configuration || {}
  options = options || {}

  var appDir = baseDir + '/app'
    , fileCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , objCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , pathCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , partialCache = {}
    , appDirs = [appDir].concat(v(function () {
        var dir = appDir + '/modules'
        return path.existsSync(dir) ? fs.readdirSync(dir) : []
      }()).map(function (dir) {
        return appDir + '/modules/' + dir
      }))
    , app = express.createServer()
    , loadFile = function (subdir, name, p) {
        if (typeof(fileCache[subdir][name]) !== 'undefined') return fileCache[subdir][name]

        var dir = v.find((p ? [p] : appDirs), function (dir) {
          var filename = dir + '/' + subdir + '/' + name + '.js'
          if (!path.existsSync(filename)) return false
          fileCache[subdir][name] = require(filename)(app, (configuration[subdir] && configuration[subdir][name] ? configuration[subdir][name] : {}))
          pathCache[subdir][name] = dir === appDir ? [appDir] : [dir, appDir]
          return true
        })
        if (!dir) throw new Error('Unable to find ' + subdir + '/' + name)

        return fileCache[subdir][name]
      }
    , loadClass = function (subdir, name, localName, definitionOnly) {
        if (definitionOnly) return loadFile(subdir, name)
        if (!objCache[subdir][name]) {
          var File = loadFile(subdir, name)
          objCache[subdir][name] = new File(localName, pathCache[subdir][name])
          objCache[subdir][name]._paths = pathCache[subdir][name]
        }
        return objCache[subdir][name]
      }
    , mountPublicDir = function (dir) {
      var directory = dir + '/public'
      path.existsSync(directory) && app.use(express.static(directory))
    }

  app.set('base_dir', appDir)
  app.set('public', appDir + '/public')
  v(appDirs).each(mountPublicDir)

  app.controllers = {
    Base: require('./BaseController')(app)
  }

  app.addModulePath = function (dir) {
    appDirs.push(dir)
    mountPublicDir(dir)
  }

  app.mount = function () {
    var router = require('./router')
      , self = this

    v.each(appDirs, function (dir) {
      var filename = dir + '/config/routes.js'
      if (!path.existsSync(filename)) return
      router.init(self, require(filename)(self))
    })
    // static directory server

    router.init(this, {
      root: [['get', /(.+)/, 'Static']]
    })
  }

  app.prefetch = function (options) {
    var self = this

    v.each(['helpers', 'models', 'services', 'controllers'], function (type) {
      v.each(appDirs, function (dir) {
        var d = dir + '/' + type
        if (!isDirectory(d)) return
        v.each(fs.readdirSync(d), function (file) {
          if (file.substr(file.length - 3) === '.js') file = file.substr(0, file.length - 3)
          loadFile(type, file, dir)
        })
      })
    })
  }

  /**
   * Finds all partial templates relative to the provided view directory, stopping once the root
   * directory has been reached.
   *
   * All templates are added from the /partials/ folder within the current view directory.  We then
   * move to the parent directory and add any partials from it's /partials/ folder that don't conflict
   * with ones already added. We then move to the next parent directory and so on until we reach the
   * application root.
   *
   * This allows views to use common templates, and be able to override sub-templates.
   *
   * Example: consider the following directory structure:
   *   app/
   *       views/
   *           partials/
   *               user-details.html  - contains {{> user-link}})
   *               user-link.html
   *           search/
   *               results.html - contains {{> user-details}}
   *               partials/
   *                   user-link.html
   *           post/
   *               post.html  - contains {{> user-details}}
   *
   * The partials for app/views/search will contain:
   *   app/views/search/partial/user-link.html
   *   app/views/partial/user-details.html
   *
   * The partials for app/views/post will only contain app/views/partials/%
   *
   *
   * TODO: This uses synchronous filesystem APIs. It should either be performed at start up
   * for all controllers or else switched to use async APIs.
   */
  app.getPartials = function (viewDir) {
    var rootDir = app.set('base_dir')
    var viewSuffix = '.' + app.set('view engine')

    if (!partialCache[viewDir]) {

      if (path.relative(rootDir, viewDir).indexOf('../') != -1) {
        throw new Error('View directories must live beneath the application root.')
      }

      var partials = {}
      while (viewDir != rootDir) {
        var partialDir = path.resolve(viewDir, 'partials')
        try {
          fs.readdirSync(partialDir).forEach(function (file) {
            // Ignore hidden files and files that don't have the right extension.
            if (file.charAt(0) == '.') return
            if (file.substr(-viewSuffix.length) != viewSuffix) return

            // Remove the suffix, such that /partials/something.html can be used as {{> something}}
            var partialName = file.substr(0, file.length - viewSuffix.length)

            // If the map already contains this partial it means it has already been specified higher
            // up the view hierarchy.
            if (!partials[partialName]) {
              var partialFilename = path.resolve(partialDir, file)
              try {
                var partialContent = fs.readFileSync(partialFilename, 'utf8')
                partials[partialName] = hogan.compile(minify(partialContent))
              } catch (e) {
                console.log('Unable to compile partial', partialFilename, e)
              }
            }
          })
        } catch (e) {
          // Only log errors if they are not a "no such file or directory" error, since we expect
          // those to happen. (Saves us an fs.statSync.)
          if (e.code != 'ENOENT') console.log('Unable to read partials directory', partialDir, e)
        }
        viewDir = path.resolve(viewDir, '../')
      }
      partialCache[viewDir] = partials
    }

    // Note: This does more work than is necessary, since common parents of views will
    // be hit once for each view. If this turns out to be problematic we can cache at
    // intermediate folders as well.

    return partialCache[viewDir]
  }

  app.getService = function (name, definitionOnly) {
    return loadClass('services', name + 'Service', name, definitionOnly)
  }

  app.getController = function (name, definitionOnly) {
    if (app.controllers[name]) {
      return definitionOnly ? app.controllers[name] : new app.controllers[name](name, [])
    }
    else {
      return loadClass('controllers', name + 'Controller', name, definitionOnly)
    }
  }

  app.getModel = function (name, definitionOnly) {
    return loadClass('models', name + 'Model', name, definitionOnly)
  }

  app.getHelper = function (name) {
    return loadFile('helpers', name + 'Helper')
  }

  app.controllers.Static = require('./StaticController')(app)

  return app
}

module.exports.engine = {
  compile: function (source, options) {
    if (typeof source !== 'string') return source
    source = minify(source)
    return function (options) {
      options.locals = options.locals || {}
      options.partials = options.partials || {}
      if (options.body) options.locals.body = options.body
      for (var i in options.partials) {
        if (v.is.fun(options.partials[i].r)) continue
        try {
          options.partials[i] = hogan.compile(options.partials[i])
        } catch (e) {
          console.log("Unable to compile partial", i, e)
        }
      }
      return hogan.compile(source, options).render(options.locals, options.partials)
    }
  }
}
<commit_msg>Make app.prefetch ignore hidden files.  Should fix #47<commit_after>var fs = require('fs')
  , express = module.exports = require('express')
  , path = require('path')
  , hogan = require('hogan.js')
  , klass = global.klass = require('klass')
  , v = global.v = require('valentine')
  , router = require('./router')
  , argv = module.exports.argv = require('optimist').argv
  , minifyViews = process.env.minify || false
var minify = function () {
  var r = /(<script[^>]*>[\s\S]+?<\/script>)/
    , scr = /^<script([^>]*)>([\s\S]+?)<\/script>/
    , white = /\s+/g
    , closeTags = />\s+</g
    , jsp = require('uglify-js').parser
    , pro = require('uglify-js').uglify
    , uglify = function (src) {
        try {
          var ast = jsp.parse(src)
          ast = pro.ast_squeeze(ast)
          return pro.gen_code(ast)
        }
        catch (ex) {
          return src
        }
      }
  return function (doc) {
    if (!minifyViews) return doc
    return doc.trim().replace(/ +/g, ' ').split(r).map(function (p, i, m) {
      return (m = p.match(scr)) ? '<script' + m[1] + '>' + uglify(m[2]) + '</script>' : p.replace(white, ' ')
    }).join('').replace(closeTags, '><')
  }
}()

function isDirectory(p) {
  try {
    return fs.statSync(p).isDirectory()
  }
  catch (ex) {
    return false
  }
}

module.exports.createApp = function (baseDir, configuration, options) {
  configuration = configuration || {}
  options = options || {}

  var appDir = baseDir + '/app'
    , fileCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , objCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , pathCache = {'services': {}, 'helpers': {}, 'models': {}, 'controllers': {}}
    , partialCache = {}
    , appDirs = [appDir].concat(v(function () {
        var dir = appDir + '/modules'
        return path.existsSync(dir) ? fs.readdirSync(dir) : []
      }()).map(function (dir) {
        return appDir + '/modules/' + dir
      }))
    , app = express.createServer()
    , loadFile = function (subdir, name, p) {
        if (typeof(fileCache[subdir][name]) !== 'undefined') return fileCache[subdir][name]

        var dir = v.find((p ? [p] : appDirs), function (dir) {
          var filename = dir + '/' + subdir + '/' + name + '.js'
          if (!path.existsSync(filename)) return false
          fileCache[subdir][name] = require(filename)(app, (configuration[subdir] && configuration[subdir][name] ? configuration[subdir][name] : {}))
          pathCache[subdir][name] = dir === appDir ? [appDir] : [dir, appDir]
          return true
        })
        if (!dir) throw new Error('Unable to find ' + subdir + '/' + name)

        return fileCache[subdir][name]
      }
    , loadClass = function (subdir, name, localName, definitionOnly) {
        if (definitionOnly) return loadFile(subdir, name)
        if (!objCache[subdir][name]) {
          var File = loadFile(subdir, name)
          objCache[subdir][name] = new File(localName, pathCache[subdir][name])
          objCache[subdir][name]._paths = pathCache[subdir][name]
        }
        return objCache[subdir][name]
      }
    , mountPublicDir = function (dir) {
      var directory = dir + '/public'
      path.existsSync(directory) && app.use(express.static(directory))
    }

  app.set('base_dir', appDir)
  app.set('public', appDir + '/public')
  v(appDirs).each(mountPublicDir)

  app.controllers = {
    Base: require('./BaseController')(app)
  }

  app.addModulePath = function (dir) {
    appDirs.push(dir)
    mountPublicDir(dir)
  }

  app.mount = function () {
    var router = require('./router')
      , self = this

    v.each(appDirs, function (dir) {
      var filename = dir + '/config/routes.js'
      if (!path.existsSync(filename)) return
      router.init(self, require(filename)(self))
    })
    // static directory server

    router.init(this, {
      root: [['get', /(.+)/, 'Static']]
    })
  }

  app.prefetch = function (options) {
    var self = this

    v.each(['helpers', 'models', 'services', 'controllers'], function (type) {
      v.each(appDirs, function (dir) {
        var d = dir + '/' + type
        if (!isDirectory(d)) return
        v.each(fs.readdirSync(d), function (file) {
          if (file.charAt(0) == '.') return
          if (file.substr(file.length - 3) === '.js') file = file.substr(0, file.length - 3)
          loadFile(type, file, dir)
        })
      })
    })
  }

  /**
   * Finds all partial templates relative to the provided view directory, stopping once the root
   * directory has been reached.
   *
   * All templates are added from the /partials/ folder within the current view directory.  We then
   * move to the parent directory and add any partials from it's /partials/ folder that don't conflict
   * with ones already added. We then move to the next parent directory and so on until we reach the
   * application root.
   *
   * This allows views to use common templates, and be able to override sub-templates.
   *
   * Example: consider the following directory structure:
   *   app/
   *       views/
   *           partials/
   *               user-details.html  - contains {{> user-link}})
   *               user-link.html
   *           search/
   *               results.html - contains {{> user-details}}
   *               partials/
   *                   user-link.html
   *           post/
   *               post.html  - contains {{> user-details}}
   *
   * The partials for app/views/search will contain:
   *   app/views/search/partial/user-link.html
   *   app/views/partial/user-details.html
   *
   * The partials for app/views/post will only contain app/views/partials/%
   *
   *
   * TODO: This uses synchronous filesystem APIs. It should either be performed at start up
   * for all controllers or else switched to use async APIs.
   */
  app.getPartials = function (viewDir) {
    var rootDir = app.set('base_dir')
    var viewSuffix = '.' + app.set('view engine')

    if (!partialCache[viewDir]) {

      if (path.relative(rootDir, viewDir).indexOf('../') != -1) {
        throw new Error('View directories must live beneath the application root.')
      }

      var partials = {}
      while (viewDir != rootDir) {
        var partialDir = path.resolve(viewDir, 'partials')
        try {
          fs.readdirSync(partialDir).forEach(function (file) {
            // Ignore hidden files and files that don't have the right extension.
            if (file.charAt(0) == '.') return
            if (file.substr(-viewSuffix.length) != viewSuffix) return

            // Remove the suffix, such that /partials/something.html can be used as {{> something}}
            var partialName = file.substr(0, file.length - viewSuffix.length)

            // If the map already contains this partial it means it has already been specified higher
            // up the view hierarchy.
            if (!partials[partialName]) {
              var partialFilename = path.resolve(partialDir, file)
              try {
                var partialContent = fs.readFileSync(partialFilename, 'utf8')
                partials[partialName] = hogan.compile(minify(partialContent))
              } catch (e) {
                console.log('Unable to compile partial', partialFilename, e)
              }
            }
          })
        } catch (e) {
          // Only log errors if they are not a "no such file or directory" error, since we expect
          // those to happen. (Saves us an fs.statSync.)
          if (e.code != 'ENOENT') console.log('Unable to read partials directory', partialDir, e)
        }
        viewDir = path.resolve(viewDir, '../')
      }
      partialCache[viewDir] = partials
    }

    // Note: This does more work than is necessary, since common parents of views will
    // be hit once for each view. If this turns out to be problematic we can cache at
    // intermediate folders as well.

    return partialCache[viewDir]
  }

  app.getService = function (name, definitionOnly) {
    return loadClass('services', name + 'Service', name, definitionOnly)
  }

  app.getController = function (name, definitionOnly) {
    if (app.controllers[name]) {
      return definitionOnly ? app.controllers[name] : new app.controllers[name](name, [])
    }
    else {
      return loadClass('controllers', name + 'Controller', name, definitionOnly)
    }
  }

  app.getModel = function (name, definitionOnly) {
    return loadClass('models', name + 'Model', name, definitionOnly)
  }

  app.getHelper = function (name) {
    return loadFile('helpers', name + 'Helper')
  }

  app.controllers.Static = require('./StaticController')(app)

  return app
}

module.exports.engine = {
  compile: function (source, options) {
    if (typeof source !== 'string') return source
    source = minify(source)
    return function (options) {
      options.locals = options.locals || {}
      options.partials = options.partials || {}
      if (options.body) options.locals.body = options.body
      for (var i in options.partials) {
        if (v.is.fun(options.partials[i].r)) continue
        try {
          options.partials[i] = hogan.compile(options.partials[i])
        } catch (e) {
          console.log("Unable to compile partial", i, e)
        }
      }
      return hogan.compile(source, options).render(options.locals, options.partials)
    }
  }
}
<|endoftext|>


<commit_before>import { createElement, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import reducerRegistry from 'app/registry';
import WebMercatorViewport from 'viewport-mercator-project';
import isEqual from 'lodash/isEqual';

import {
  setInteraction,
  clearInteractions
} from 'components/maps/map/components/popup/actions';
import * as ownActions from './actions';
import reducers, { initialState } from './reducers';
import { getMapProps } from './selectors';
import MapComponent from './component';

const actions = {
  setInteraction,
  clearInteractions,
  ...ownActions
};

class MapContainer extends PureComponent {
  state = {
    bbox: null,
    width: 0,
    height: 0,
    map: null
  };

  componentDidUpdate(prevProps, prevState) {
    const {
      canBound,
      bbox,
      geostoreBbox,
      setMapSettings,
      layerBbox,
      selectedInteraction
    } = this.props;

    // only set bounding box if action allows it
    if (canBound && bbox !== prevProps.bbox) {
      this.setBbox(bbox);
      this.props.clearInteractions();
    }

    if (this.state.bbox && this.state.bbox !== prevState.bbox) {
      this.fitBounds(this.state.bbox);
    }

    // if a new layer contains a bbox
    if (layerBbox && layerBbox !== prevProps.layerBbox) {
      setMapSettings({ bbox: layerBbox });
    }

    // if geostore changes
    if (geostoreBbox && geostoreBbox !== prevProps.geostoreBbox) {
      setMapSettings({ bbox: geostoreBbox });
    }

    // fit bounds on cluster if clicked
    if (
      selectedInteraction &&
      !isEqual(selectedInteraction, prevProps.selectedInteraction) &&
      selectedInteraction.data.cluster
    ) {
      const { data, layer, geometry } = selectedInteraction;
      this.state.map
        .getSource(layer.id)
        .getClusterExpansionZoom(data.cluster_id, (err, newZoom) => {
          if (err) return;
          const { coordinates } = geometry;
          this.setMapPosition(coordinates[1], coordinates[0], newZoom);
        });
    }
  }

  setMapPosition = (lat, lng, zoom) => {
    const { setMapSettings } = this.props;
    const { width, height } = this.state;
    if (width && height) {
      const newViewport = new WebMercatorViewport({
        width,
        height,
        longitude: lng,
        latitude: lat,
        zoom,
        pitch: 0,
        bearing: 0
      });
      const { latitude, longitude } = newViewport;
      setMapSettings({
        center: {
          lat: latitude,
          lng: longitude
        },
        zoom: newViewport.zoom
      });
    }
  };

  setBbox = bbox => {
    this.setState({ bbox });
  };

  setMapRect = map => {
    if (map && !this.state.width && !this.state.height) {
      const mapEl = map.getBoundingClientRect();
      this.setState({ width: mapEl.width, height: mapEl.height });
    }
  };

  setMap = map => {
    this.setState({ map });
  };

  fitBounds = bbox => {
    const { lat, lng, zoom, setMapSettings } = this.props;
    const { width, height } = this.state;
    if (bbox && width && height) {
      const viewport = new WebMercatorViewport({
        width,
        height,
        longitude: lng,
        latitude: lat,
        zoom,
        pitch: 0,
        bearing: 0
      });
      const newViewport = viewport.fitBounds(
        [[bbox[0], bbox[1]], [bbox[2], bbox[3]]],
        {
          padding: 50
        }
      );
      const { latitude, longitude } = newViewport;
      setMapSettings({
        center: {
          lat: latitude,
          lng: longitude
        },
        zoom: newViewport.zoom
      });
    }
  };

  handleMapMove = viewport => {
    const { latitude, longitude, zoom } = viewport;
    const { setMapSettings, mapOptions: { maxZoom, minZoom } } = this.props;
    let newZoom = zoom;
    if (zoom > maxZoom) newZoom = maxZoom;
    if (zoom < minZoom) newZoom = minZoom;

    setMapSettings({
      zoom: newZoom,
      center: {
        lat: latitude,
        lng: longitude
      },
      canBound: false,
      bbox: null
    });

    this.setBbox(null);
  };

  handleMapInteraction = e => {
    const { draw, menuSection } = this.props;
    if (!draw && !menuSection && e.features && e.features.length) {
      this.props.setInteraction(e);
    }
  };

  render() {
    return createElement(MapComponent, {
      ...this.props,
      ...this.state,
      handleMapInteraction: this.handleMapInteraction,
      handleMapMove: this.handleMapMove,
      setBbox: this.setBbox,
      setMapRect: this.setMapRect,
      setMap: this.setMap
    });
  }
}

MapContainer.propTypes = {
  canBound: PropTypes.bool,
  bbox: PropTypes.array,
  geostoreBbox: PropTypes.array,
  setMapSettings: PropTypes.func,
  mapOptions: PropTypes.object,
  setInteraction: PropTypes.func,
  layerBbox: PropTypes.array,
  draw: PropTypes.bool,
  menuSection: PropTypes.string,
  lat: PropTypes.number,
  lng: PropTypes.number,
  zoom: PropTypes.number,
  clearInteractions: PropTypes.func,
  selectedInteraction: PropTypes.object
};

reducerRegistry.registerModule('map', {
  actions: ownActions,
  reducers,
  initialState
});

export default connect(getMapProps, actions)(MapContainer);
<commit_msg>sync map center and zoom with state and debouce url update<commit_after>import { createElement, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import reducerRegistry from 'app/registry';
import WebMercatorViewport from 'viewport-mercator-project';
import isEqual from 'lodash/isEqual';
import debounce from 'lodash/debounce';

import {
  setInteraction,
  clearInteractions
} from 'components/maps/map/components/popup/actions';
import * as ownActions from './actions';
import reducers, { initialState } from './reducers';
import { getMapProps } from './selectors';
import MapComponent from './component';

const actions = {
  setInteraction,
  clearInteractions,
  ...ownActions
};

class MapContainer extends PureComponent {
  state = {
    bbox: null,
    width: 0,
    height: 0,
    map: null,
    zoom: this.props.zoom,
    lat: this.props.lat,
    lng: this.props.lng
  };

  componentDidUpdate(prevProps, prevState) {
    const {
      canBound,
      bbox,
      geostoreBbox,
      setMapSettings,
      layerBbox,
      selectedInteraction
    } = this.props;

    // only set bounding box if action allows it
    if (canBound && bbox !== prevProps.bbox) {
      this.setBbox(bbox);
      this.props.clearInteractions();
    }

    if (this.state.bbox && this.state.bbox !== prevState.bbox) {
      this.fitBounds(this.state.bbox);
    }

    // if a new layer contains a bbox
    if (layerBbox && layerBbox !== prevProps.layerBbox) {
      setMapSettings({ bbox: layerBbox });
    }

    // if geostore changes
    if (geostoreBbox && geostoreBbox !== prevProps.geostoreBbox) {
      setMapSettings({ bbox: geostoreBbox });
    }

    // fit bounds on cluster if clicked
    if (
      selectedInteraction &&
      !isEqual(selectedInteraction, prevProps.selectedInteraction) &&
      selectedInteraction.data.cluster
    ) {
      const { data, layer, geometry } = selectedInteraction;
      this.state.map
        .getSource(layer.id)
        .getClusterExpansionZoom(data.cluster_id, (err, newZoom) => {
          if (err) return;
          const { coordinates } = geometry;
          this.setMapPosition(coordinates[1], coordinates[0], newZoom);
        });
    }
  }

  setMapPosition = (lat, lng, zoom) => {
    const { setMapSettings } = this.props;
    const { width, height } = this.state;
    if (width && height) {
      const newViewport = new WebMercatorViewport({
        width,
        height,
        longitude: lng,
        latitude: lat,
        zoom,
        pitch: 0,
        bearing: 0
      });
      const { latitude, longitude } = newViewport;
      setMapSettings({
        center: {
          lat: latitude,
          lng: longitude
        },
        zoom: newViewport.zoom
      });
    }
  };

  setBbox = bbox => {
    this.setState({ bbox });
  };

  setMapRect = map => {
    if (map && !this.state.width && !this.state.height) {
      const mapEl = map.getBoundingClientRect();
      this.setState({ width: mapEl.width, height: mapEl.height });
    }
  };

  setMap = map => {
    this.setState({ map });
  };

  fitBounds = bbox => {
    const { lat, lng, zoom, setMapSettings } = this.props;
    const { width, height } = this.state;
    if (bbox && width && height) {
      const viewport = new WebMercatorViewport({
        width,
        height,
        longitude: lng,
        latitude: lat,
        zoom,
        pitch: 0,
        bearing: 0
      });
      const newViewport = viewport.fitBounds(
        [[bbox[0], bbox[1]], [bbox[2], bbox[3]]],
        {
          padding: 50
        }
      );
      const { latitude, longitude } = newViewport;
      setMapSettings({
        center: {
          lat: latitude,
          lng: longitude
        },
        zoom: newViewport.zoom
      });
    }
  };

  handleMapMove = viewport => {
    const { latitude, longitude, zoom } = viewport;
    const { mapOptions: { maxZoom, minZoom } } = this.props;
    let newZoom = zoom;
    if (zoom > maxZoom) newZoom = maxZoom;
    if (zoom < minZoom) newZoom = minZoom;

    this.setState({
      zoom: newZoom,
      lat: latitude,
      lng: longitude
    });

    this.setMapViewport({
      zoom: newZoom,
      center: {
        lat: latitude,
        lng: longitude
      }
    });

    this.setBbox(null);
  };

  setMapViewport = debounce(view => {
    this.props.setMapSettings({
      ...view,
      canBound: false,
      bbox: null
    });
  }, 300);

  handleMapInteraction = e => {
    const { draw, menuSection } = this.props;
    if (!draw && !menuSection && e.features && e.features.length) {
      this.props.setInteraction(e);
    }
  };

  render() {
    return createElement(MapComponent, {
      ...this.props,
      ...this.state,
      handleMapInteraction: this.handleMapInteraction,
      handleMapMove: this.handleMapMove,
      setBbox: this.setBbox,
      setMapRect: this.setMapRect,
      setMap: this.setMap
    });
  }
}

MapContainer.propTypes = {
  canBound: PropTypes.bool,
  bbox: PropTypes.array,
  geostoreBbox: PropTypes.array,
  setMapSettings: PropTypes.func,
  mapOptions: PropTypes.object,
  setInteraction: PropTypes.func,
  layerBbox: PropTypes.array,
  draw: PropTypes.bool,
  menuSection: PropTypes.string,
  lat: PropTypes.number,
  lng: PropTypes.number,
  zoom: PropTypes.number,
  clearInteractions: PropTypes.func,
  selectedInteraction: PropTypes.object
};

reducerRegistry.registerModule('map', {
  actions: ownActions,
  reducers,
  initialState
});

export default connect(getMapProps, actions)(MapContainer);
<|endoftext|>


<commit_before><commit_msg>무언가 수정되었습니다.<commit_after><|endoftext|>


<commit_before>import React from 'react'
import Formsy from 'formsy-react'
import Modal from 'react-modal';
import MyInput from '../components/my-input'
import MySelect from '../components/my-select'
import MyTextarea from '../components/my-textarea'
import SectionTitle from '../components/section-title'
import HttpHelpers from '../utils/http-helpers'
var _ = require('lodash');

const redirectUrl = (window.location.href.split("/\?", 2)[0] + '/step2').replace("//step2", "/step2");
const randomEmail = Math.random().toString(36).substr(2,10);

const FormContainer = React.createClass({
  getInitialState() {
    return {
      canSubmit: false,
      isLoading: false,
      validationErrors: {},
      serverErrorMsg: false,
      serverSuccessMsg: false,
      showSubmissionModal: false,
      loading: {
        'street': false,
        'suburb': false,
      },
      suburbs: ['Enter Postcode...'],
      streets: ['Choose Suburb...'],
      postcode: '0000',
      country: 'au',
    };
  },
  submit(data) {
    if (data.mnames === undefined) { data.mnames = ""}
    if (data.referred_by === undefined) { data.referred_by = ""}
    if (data.member_comment === undefined) { data.member_comment = ""}
    data.dob = data.dobYear + '-' + data.dobMonth + '-' + data.dobDay + 'T12:00:00';
    data.address = _.join([data.addr_street_no, data.addr_street, data.addr_suburb, data.addr_postcode], "; ");
    data.name = _.join([data.fname, data.mnames, data.sname], " ");

    this.setState({isLoading: true, showSubmissionModal: true});
    HttpHelpers.sendForm(data, function(response){

      if (__DEV__) {
        console.log(response);
        console.log(data);
      }

      if (response.ok && response.status === 200 && response.body.success === true) {
        this.setState({
          isLoading: false,
          serverSuccessMsg: "Success"
        });
        setTimeout(function() {
          window.location.assign(redirectUrl)
        }, 1500);

        ga('send', {hitType: 'event', eventCategory: 'Membership Form', eventAction: 'Submission'});
        fbq('track', 'Complete Registration');
        fbq('track', 'NewMember');  // a different pixel thing to the above, keep both.

      } else if (response.status === 409) {
        console.log('Duplicate email detected');
        this.setState({
          isLoading: false,
          serverErrorMsg: "Error. Email already exists. Please update details instead of re-registering."
        });
      } else {
        this.setState({
          isLoading: false,
          serverErrorMsg: "Server error, " + response.statusText ? response.statusText : null
        });
      }
    }.bind(this))
  },
  enableButton() {
    this.setState({ canSubmit: true });
  },
  disableButton() {
    this.setState({ canSubmit: false });
  },
  closeModal() {
    this.setState({ showSubmissionModal: false });
  },

  checkPC(evt) {
    const pcTest = /\d{4}/;
    const pc = evt.currentTarget.value;
    console.log(pc);
    if (pcTest.test(pc)) {
      this.updateSuburbs(pc);
    }
  },

  checkSuburb(evt) {
    const suburb = evt.currentTarget.value;
    this.updateStreets(suburb);
  },

  updateSuburbs(pc){
    this.setState({loading: { suburb: true }, suburbs: ['Loading...'], postcode: pc});
    const outer = this;
    const setStateUnk = function() { outer.setState({suburbs: ["Unknown Postcode"]}); };
    HttpHelpers.getSuburbs(pc, function(err, resp){
      if (err) {
        console.log(err);
        setStateUnk();
      } else {
        console.log(resp);
        if (resp.body.suburbs.length == 0)
          setStateUnk();
        else {
          outer.setState({suburbs: _.concat(["Choose Suburb..."], resp.body.suburbs)});
          outer.updateStreets(resp.body.suburbs[0]);
        }
      }
    });
  },

  updateStreets(suburb){
    const outer = this;
    this.setState({loading: { street: true }, streets:["Loading..."], suburb: suburb});
    HttpHelpers.getStreets(this.state.postcode, suburb, function(err, resp){
      if(err){
        console.log(err);
        outer.setState({streets:["Unknown Suburb"]});
      } else {
        console.log(resp);
        outer.setState({streets: _.concat(["Choose Street..."], resp.body.streets)});
      }
    })
  },

  writeToState(_key){
    return function(evt){
      let newState = {};
      newState[_key] = evt.currentTarget.value;
      this.setState(newState);
    }
  },

  render() {
    return (
      <Formsy.Form
        onSubmit={this.submit}
        onValid={this.enableButton}
        onInvalid={this.disableButton}
        onChange={this.validateForm}>

Sign up below, it's quick and easy

              Please make sure your details exactly match those on the electoral roll. You can confirm them on the
               AEC website.

          <MyInput
            inputClass="checkbox"
            type="checkbox"
            name="onAECRoll"
            title="I am on the Australian Electoral Roll."
            validationError="First name is required"
            value={false} />

          <SectionTitle text="1. Name"/>

          <MyInput
            inputClass="input"
            name="fname"
            title="Legal first name"
            autocomplete="given-name"
            value={__DEV__ ? "asdf" : ""}
            validationErrors={{
              isRequired: 'First name is required'
            }}
            required />

            <MyInput
              inputClass="input"
              name="mnames"
              autocomplete="false"
              title="Legal middle name"
              value={__DEV__ ? "asdf" : ""}
              validationErrors={{
                isSpecialWords: 'Only letters please'
              }}
              formNoValidate />

          <MyInput
            inputClass="input"
            name="sname"
            autocomplete="family-name"
            title="Legal last name"
            value={__DEV__ ? "asdf" : ""}
            validationErrors={{
              isRequired: 'First name is required'
            }}
            required />

          <SectionTitle text="2. Address"/>

          <MySelect
            inputClass="input"
            name="addr_country"
            title="Country"
            validationError="Country Required"
            options={[{title: 'Australia', value: 'au'}]}
            onChange={this.writeToState('country')}
            />

          <MyInput
            inputClass="input"
            name="addr_postcode"
            title="Postcode"
            validations={_.includes(['au'], this.state.country) ? "isNumeric,isLength:4" : null}
            pattern={_.includes(['au'], this.state.country) ? "\\d*" : ""}
            value={__DEV__ ? "2000" : ""}
            validationErrors={{
              isRequired: 'Postcode required',
              isNumeric: 'Only numbers allowed',
              isLength: 'Length needs to be 4 digits'
            }}
            onChange={this.checkPC}
            required />

          <MySelect
            inputClass="input"
            name="addr_suburb"
            title="Suburb"
            validationError="Suburb required"
            options={this.state.suburbs}
            value={__DEV__ ? "HAYMARKET (NSW)" : ""}
            disabled={this.state.suburbs[0] == 'Loading...'}
            onChange={this.checkSuburb}
            validations={{
              notDefault: function(values, value){
                return !_.includes(value, '...') ? true : "Must choose a suburb";
              }
            }}
            required />

            <MyInput
              inputClass="input"
              className="col-4 inline-block px1"
              name="addr_street_no"
              title="Unit / Street No."
              validationError="Unit and Street Number required"
              placeholder="eg: u32 / 117"
              value={__DEV__ ? "13 / 37" : ""}
              required
              />

            <MySelect
              className="col-8 mx-auto inline-block px1 absolute bottom-0 right-0 mb1 "
              inputClass="input"
              name="addr_street"
              title="Street"
              validationError="Street address is required"
              value={__DEV__ ? "asdf" : ""}
              disabled={this.state.streets[0] == 'Loading...'}
              validationErrors={{
                isRequired: 'Street address required'
              }}
              options={this.state.streets}
              required />

          <SectionTitle text="3. Date of Birth" infoText="(Required by AEC)"/>

              <MyInput
                inputClass="input"
                name="dobDay"
                title="Day"
                placeholder="DD"
                pattern="\d*"
                value={__DEV__ ? "01" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 2,
                  matchRegexp: /(0[1-9]|[12]\d|3[01])/
                }}
                validationErrors={{
                  isRequired: 'Day required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Length must be 2 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

              <MyInput
                inputClass="input"
                name="dobMonth"
                title="Month"
                placeholder="MM"
                pattern="\d*"
                value={__DEV__ ? "01" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 2,
                  matchRegexp: /^(0[1-9]|1[0-2])$/
                }}
                validationErrors={{
                  isRequired: 'Month required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Month must be 2 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

              <MyInput
                inputClass="input"
                name="dobYear"
                title="Year"
                placeholder="YYYY"
                pattern="\d*"
                value={__DEV__ ? "2000" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 4,
                  matchRegexp: /^(190[0-9]|19[1-9]\d|200\d|201[0-6])$/
                }}
                validationErrors={{
                  isRequired: 'Year required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Year must be 4 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

          <SectionTitle text="4. Contact details"/>
          <MyInput
            inputClass="input"
            type="tel"
            name="contact_number"
            pattern="\d*"
            title="Phone"
            value={__DEV__ ? "1234567890" : ""}
            validations={{
              minLength: 8
            }}
            validationErrors={{
              isRequired: 'Phone number is required',
              minLength: 'Needs to be at least 8 digits.'
            }}
            required />

          <MyInput
            inputClass="input"
            name="email"
            title="Email"
            type="email"
            pattern="[^ @]*@[^ @]*"
            value={__DEV__ ? randomEmail + "@xk.io" : ""}
            validations="isEmail"
            validationError="This is not a valid email"
            autocomplete="email"
            required />

          <SectionTitle text="5. Get Involved"/>

          { this.props.onReferrer
            ?
            <MyInput
              inputClass="input hide"
              name="referred_by"
              value={this.props.onReferrer} />
             :

                <MyInput
                  inputClass="input"
                  name="referred_by"
                  title="How did you hear about Flux?"
                  subtext="(Optional)" />

}

          {/*<MyInput
            inputClass="input"
            type="checkbox"
            name="mailing-list"
            title="Send me Flux news and important updates."
            value={true}
             />*/}

         <MyTextarea
             rows="3"
             inputClass="input"
             name="member_comment"
             title="Comments / Notes"
             value={__DEV__ ? "DEV SUBMISSION - PLEASE DELETE" : ""}
             subtext="(Optional, Anything you'd like to add, about yourself, us, or anything, really.)" />

           <MyInput
             inputClass="input"
             type="checkbox"
             name="volunteer"
             title="I'm interested in volunteering"
             value={false}
              />

              <button type="submit" className="h3 btn btn-primary" disabled={!this.state.canSubmit}>Submit chevron_right</button>

            {!this.state.canSubmit
              &&

                *

You can't submit until you fill out all required fields

}

             <Modal
              isOpen={this.state.showSubmissionModal}
              contentLabel="Processing Registration"
              onRequestClose={this.closeModal}
              style={{
                overlay: {
                  backgroundColor: "rgba(20, 20, 20, 0.5)",
                },
                content: {

                }
              }}
             >
               {this.state.isLoading
                ?

Registering...

                : this.state.serverSuccessMsg
                ?

{this.state.serverSuccessMsg}

                    Redirecting...

                : this.state.serverErrorMsg
                ?

{this.state.serverErrorMsg}

                    <button className="btn btn-primary mx-auto h1" onClick={this.closeModal}>Close</button>

                : null}
             </Modal>

            lock
            Your details will be kept absolutely confidential and will only be used for party business.

      </Formsy.Form>
    );
  }
});

export default FormContainer;
<commit_msg>Update AEC url<commit_after>import React from 'react'
import Formsy from 'formsy-react'
import Modal from 'react-modal';
import MyInput from '../components/my-input'
import MySelect from '../components/my-select'
import MyTextarea from '../components/my-textarea'
import SectionTitle from '../components/section-title'
import HttpHelpers from '../utils/http-helpers'
var _ = require('lodash');

const redirectUrl = (window.location.href.split("/\?", 2)[0] + '/step2').replace("//step2", "/step2");
const randomEmail = Math.random().toString(36).substr(2,10);

const FormContainer = React.createClass({
  getInitialState() {
    return {
      canSubmit: false,
      isLoading: false,
      validationErrors: {},
      serverErrorMsg: false,
      serverSuccessMsg: false,
      showSubmissionModal: false,
      loading: {
        'street': false,
        'suburb': false,
      },
      suburbs: ['Enter Postcode...'],
      streets: ['Choose Suburb...'],
      postcode: '0000',
      country: 'au',
    };
  },
  submit(data) {
    if (data.mnames === undefined) { data.mnames = ""}
    if (data.referred_by === undefined) { data.referred_by = ""}
    if (data.member_comment === undefined) { data.member_comment = ""}
    data.dob = data.dobYear + '-' + data.dobMonth + '-' + data.dobDay + 'T12:00:00';
    data.address = _.join([data.addr_street_no, data.addr_street, data.addr_suburb, data.addr_postcode], "; ");
    data.name = _.join([data.fname, data.mnames, data.sname], " ");

    this.setState({isLoading: true, showSubmissionModal: true});
    HttpHelpers.sendForm(data, function(response){

      if (__DEV__) {
        console.log(response);
        console.log(data);
      }

      if (response.ok && response.status === 200 && response.body.success === true) {
        this.setState({
          isLoading: false,
          serverSuccessMsg: "Success"
        });
        setTimeout(function() {
          window.location.assign(redirectUrl)
        }, 1500);

        ga('send', {hitType: 'event', eventCategory: 'Membership Form', eventAction: 'Submission'});
        fbq('track', 'Complete Registration');
        fbq('track', 'NewMember');  // a different pixel thing to the above, keep both.

      } else if (response.status === 409) {
        console.log('Duplicate email detected');
        this.setState({
          isLoading: false,
          serverErrorMsg: "Error. Email already exists. Please update details instead of re-registering."
        });
      } else {
        this.setState({
          isLoading: false,
          serverErrorMsg: "Server error, " + response.statusText ? response.statusText : null
        });
      }
    }.bind(this))
  },
  enableButton() {
    this.setState({ canSubmit: true });
  },
  disableButton() {
    this.setState({ canSubmit: false });
  },
  closeModal() {
    this.setState({ showSubmissionModal: false });
  },

  checkPC(evt) {
    const pcTest = /\d{4}/;
    const pc = evt.currentTarget.value;
    console.log(pc);
    if (pcTest.test(pc)) {
      this.updateSuburbs(pc);
    }
  },

  checkSuburb(evt) {
    const suburb = evt.currentTarget.value;
    this.updateStreets(suburb);
  },

  updateSuburbs(pc){
    this.setState({loading: { suburb: true }, suburbs: ['Loading...'], postcode: pc});
    const outer = this;
    const setStateUnk = function() { outer.setState({suburbs: ["Unknown Postcode"]}); };
    HttpHelpers.getSuburbs(pc, function(err, resp){
      if (err) {
        console.log(err);
        setStateUnk();
      } else {
        console.log(resp);
        if (resp.body.suburbs.length == 0)
          setStateUnk();
        else {
          outer.setState({suburbs: _.concat(["Choose Suburb..."], resp.body.suburbs)});
          outer.updateStreets(resp.body.suburbs[0]);
        }
      }
    });
  },

  updateStreets(suburb){
    const outer = this;
    this.setState({loading: { street: true }, streets:["Loading..."], suburb: suburb});
    HttpHelpers.getStreets(this.state.postcode, suburb, function(err, resp){
      if(err){
        console.log(err);
        outer.setState({streets:["Unknown Suburb"]});
      } else {
        console.log(resp);
        outer.setState({streets: _.concat(["Choose Street..."], resp.body.streets)});
      }
    })
  },

  writeToState(_key){
    return function(evt){
      let newState = {};
      newState[_key] = evt.currentTarget.value;
      this.setState(newState);
    }
  },

  render() {
    return (
      <Formsy.Form
        onSubmit={this.submit}
        onValid={this.enableButton}
        onInvalid={this.disableButton}
        onChange={this.validateForm}>

Sign up below, it's quick and easy

              Please make sure your details exactly match those on the electoral roll. You can confirm them on the
              AEC website.

          <MyInput
            inputClass="checkbox"
            type="checkbox"
            name="onAECRoll"
            title="I am on the Australian Electoral Roll."
            validationError="First name is required"
            value={false} />

          <SectionTitle text="1. Name"/>

          <MyInput
            inputClass="input"
            name="fname"
            title="Legal first name"
            autocomplete="given-name"
            value={__DEV__ ? "asdf" : ""}
            validationErrors={{
              isRequired: 'First name is required'
            }}
            required />

            <MyInput
              inputClass="input"
              name="mnames"
              autocomplete="false"
              title="Legal middle name"
              value={__DEV__ ? "asdf" : ""}
              validationErrors={{
                isSpecialWords: 'Only letters please'
              }}
              formNoValidate />

          <MyInput
            inputClass="input"
            name="sname"
            autocomplete="family-name"
            title="Legal last name"
            value={__DEV__ ? "asdf" : ""}
            validationErrors={{
              isRequired: 'First name is required'
            }}
            required />

          <SectionTitle text="2. Address"/>

          <MySelect
            inputClass="input"
            name="addr_country"
            title="Country"
            validationError="Country Required"
            options={[{title: 'Australia', value: 'au'}]}
            onChange={this.writeToState('country')}
            />

          <MyInput
            inputClass="input"
            name="addr_postcode"
            title="Postcode"
            validations={_.includes(['au'], this.state.country) ? "isNumeric,isLength:4" : null}
            pattern={_.includes(['au'], this.state.country) ? "\\d*" : ""}
            value={__DEV__ ? "2000" : ""}
            validationErrors={{
              isRequired: 'Postcode required',
              isNumeric: 'Only numbers allowed',
              isLength: 'Length needs to be 4 digits'
            }}
            onChange={this.checkPC}
            required />

          <MySelect
            inputClass="input"
            name="addr_suburb"
            title="Suburb"
            validationError="Suburb required"
            options={this.state.suburbs}
            value={__DEV__ ? "HAYMARKET (NSW)" : ""}
            disabled={this.state.suburbs[0] == 'Loading...'}
            onChange={this.checkSuburb}
            validations={{
              notDefault: function(values, value){
                return !_.includes(value, '...') ? true : "Must choose a suburb";
              }
            }}
            required />

            <MyInput
              inputClass="input"
              className="col-4 inline-block px1"
              name="addr_street_no"
              title="Unit / Street No."
              validationError="Unit and Street Number required"
              placeholder="eg: u32 / 117"
              value={__DEV__ ? "13 / 37" : ""}
              required
              />

            <MySelect
              className="col-8 mx-auto inline-block px1 absolute bottom-0 right-0 mb1 "
              inputClass="input"
              name="addr_street"
              title="Street"
              validationError="Street address is required"
              value={__DEV__ ? "asdf" : ""}
              disabled={this.state.streets[0] == 'Loading...'}
              validationErrors={{
                isRequired: 'Street address required'
              }}
              options={this.state.streets}
              required />

          <SectionTitle text="3. Date of Birth" infoText="(Required by AEC)"/>

              <MyInput
                inputClass="input"
                name="dobDay"
                title="Day"
                placeholder="DD"
                pattern="\d*"
                value={__DEV__ ? "01" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 2,
                  matchRegexp: /(0[1-9]|[12]\d|3[01])/
                }}
                validationErrors={{
                  isRequired: 'Day required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Length must be 2 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

              <MyInput
                inputClass="input"
                name="dobMonth"
                title="Month"
                placeholder="MM"
                pattern="\d*"
                value={__DEV__ ? "01" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 2,
                  matchRegexp: /^(0[1-9]|1[0-2])$/
                }}
                validationErrors={{
                  isRequired: 'Month required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Month must be 2 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

              <MyInput
                inputClass="input"
                name="dobYear"
                title="Year"
                placeholder="YYYY"
                pattern="\d*"
                value={__DEV__ ? "2000" : ""}
                validations={{
                  isNumeric: true,
                  isLength: 4,
                  matchRegexp: /^(190[0-9]|19[1-9]\d|200\d|201[0-6])$/
                }}
                validationErrors={{
                  isRequired: 'Year required',
                  isNumeric: 'Only numbers allowed',
                  isLength: 'Year must be 4 digits',
                  matchRegexp: 'Invalid date range'
                }}
                required />

          <SectionTitle text="4. Contact details"/>
          <MyInput
            inputClass="input"
            type="tel"
            name="contact_number"
            pattern="\d*"
            title="Phone"
            value={__DEV__ ? "1234567890" : ""}
            validations={{
              minLength: 8
            }}
            validationErrors={{
              isRequired: 'Phone number is required',
              minLength: 'Needs to be at least 8 digits.'
            }}
            required />

          <MyInput
            inputClass="input"
            name="email"
            title="Email"
            type="email"
            pattern="[^ @]*@[^ @]*"
            value={__DEV__ ? randomEmail + "@xk.io" : ""}
            validations="isEmail"
            validationError="This is not a valid email"
            autocomplete="email"
            required />

          <SectionTitle text="5. Get Involved"/>

          { this.props.onReferrer
            ?
            <MyInput
              inputClass="input hide"
              name="referred_by"
              value={this.props.onReferrer} />
             :

                <MyInput
                  inputClass="input"
                  name="referred_by"
                  title="How did you hear about Flux?"
                  subtext="(Optional)" />

}

          {/*<MyInput
            inputClass="input"
            type="checkbox"
            name="mailing-list"
            title="Send me Flux news and important updates."
            value={true}
             />*/}

         <MyTextarea
             rows="3"
             inputClass="input"
             name="member_comment"
             title="Comments / Notes"
             value={__DEV__ ? "DEV SUBMISSION - PLEASE DELETE" : ""}
             subtext="(Optional, Anything you'd like to add, about yourself, us, or anything, really.)" />

           <MyInput
             inputClass="input"
             type="checkbox"
             name="volunteer"
             title="I'm interested in volunteering"
             value={false}
              />

              <button type="submit" className="h3 btn btn-primary" disabled={!this.state.canSubmit}>Submit chevron_right</button>

            {!this.state.canSubmit
              &&

                *

You can't submit until you fill out all required fields

}

             <Modal
              isOpen={this.state.showSubmissionModal}
              contentLabel="Processing Registration"
              onRequestClose={this.closeModal}
              style={{
                overlay: {
                  backgroundColor: "rgba(20, 20, 20, 0.5)",
                },
                content: {

                }
              }}
             >
               {this.state.isLoading
                ?

Registering...

                : this.state.serverSuccessMsg
                ?

{this.state.serverSuccessMsg}

                    Redirecting...

                : this.state.serverErrorMsg
                ?

{this.state.serverErrorMsg}

                    <button className="btn btn-primary mx-auto h1" onClick={this.closeModal}>Close</button>

                : null}
             </Modal>

            lock
            Your details will be kept absolutely confidential and will only be used for party business.

      </Formsy.Form>
    );
  }
});

export default FormContainer;
<|endoftext|>


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

/**
 * Construct a token.
 *
 * Based on a JSON Web Token string, construct a token object. Optionally
 * if a `clientId` is provided, the token may be tested for roles with
 * `hasRole()`.
 *
 * @constructor
 *
 * @param {String} token The JSON Web Token formatted token string.
 * @param {String} clientId Optional clientId if this is an `access_token`.
 */
function Token (token, clientId) {
  this.token = token;
  this.clientId = clientId;

  if (token) {
    try {
      const parts = token.split('.');
      this.header = JSON.parse(new Buffer(parts[0], 'base64').toString());
      this.content = JSON.parse(new Buffer(parts[1], 'base64').toString());
      this.signature = new Buffer(parts[2], 'base64');
      this.signed = parts[0] + '.' + parts[1];
    } catch (err) {
      this.content = {
        expiresAt: 0
      };
    }
  }
}

/**
 * Determine if this token is expired.
 *
 * @return {boolean} `true` if it is expired, otherwise `false`.
 */
Token.prototype.isExpired = function isExpired () {
  return ((this.content.exp * 1000) < Date.now());
};

/**
 * Determine if this token has an associated role.
 *
 * This method is only functional if the token is constructed
 * with a `clientId` parameter.
 *
 * The parameter matches a role specification using the following rules:
 *
 * - If the name contains no colons, then the name is taken as the entire
 *   name of a role within the current application, as specified via
 *   `clientId`.
 * - If the name starts with the literal `realm:`, the subsequent portion
 *   is taken as the name of a _realm-level_ role.
 * - Otherwise, the name is split at the colon, with the first portion being
 *   taken as the name of an arbitrary application, and the subsequent portion
 *   as the name of a role with that app.
 *
 * @param {String} name The role name specifier.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasRole = function hasRole (name) {
  if (!this.clientId) {
    return false;
  }

  const parts = name.split(':');
  if (parts.length === 1) {
    return this.hasApplicationRole(this.clientId, parts[0]);
  }

  if (parts[0] === 'realm') {
    return this.hasRealmRole(parts[1]);
  }

  return this.hasApplicationRole(parts[0], parts[1]);
};

/**
 * Determine if this token has an associated specific application role.
 *
 * Even if `clientId` is not set, this method may be used to explicitly test
 * roles for any given application.
 *
 * @param {String} appName The identifier of the application to test.
 * @param {String} roleName The name of the role within that application to test.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasApplicationRole = function hasApplicationRole (appName, roleName) {
  var appRoles = this.content.resource_access[appName];

  if (!appRoles) {
    return false;
  }

  return (appRoles.roles.indexOf(roleName) >= 0);
};

/**
 * Determine if this token has an associated specific realm-level role.
 *
 * Even if `clientId` is not set, this method may be used to explicitly test
 * roles for the realm.
 *
 * @param {String} appName The identifier of the application to test.
 * @param {String} roleName The name of the role within that application to test.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasRealmRole = function hasRealmRole (roleName) {
  return (this.content.realm_access.roles.indexOf(roleName) >= 0);
};

module.exports = Token;
<commit_msg>Fixed wrong JWT token structure with expiration property<commit_after>/*!
 * Copyright 2014 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

/**
 * Construct a token.
 *
 * Based on a JSON Web Token string, construct a token object. Optionally
 * if a `clientId` is provided, the token may be tested for roles with
 * `hasRole()`.
 *
 * @constructor
 *
 * @param {String} token The JSON Web Token formatted token string.
 * @param {String} clientId Optional clientId if this is an `access_token`.
 */
function Token (token, clientId) {
  this.token = token;
  this.clientId = clientId;

  if (token) {
    try {
      const parts = token.split('.');
      this.header = JSON.parse(new Buffer(parts[0], 'base64').toString());
      this.content = JSON.parse(new Buffer(parts[1], 'base64').toString());
      this.signature = new Buffer(parts[2], 'base64');
      this.signed = parts[0] + '.' + parts[1];
    } catch (err) {
      this.content = {
        exp: 0
      };
    }
  }
}

/**
 * Determine if this token is expired.
 *
 * @return {boolean} `true` if it is expired, otherwise `false`.
 */
Token.prototype.isExpired = function isExpired () {
  return ((this.content.exp * 1000) < Date.now());
};

/**
 * Determine if this token has an associated role.
 *
 * This method is only functional if the token is constructed
 * with a `clientId` parameter.
 *
 * The parameter matches a role specification using the following rules:
 *
 * - If the name contains no colons, then the name is taken as the entire
 *   name of a role within the current application, as specified via
 *   `clientId`.
 * - If the name starts with the literal `realm:`, the subsequent portion
 *   is taken as the name of a _realm-level_ role.
 * - Otherwise, the name is split at the colon, with the first portion being
 *   taken as the name of an arbitrary application, and the subsequent portion
 *   as the name of a role with that app.
 *
 * @param {String} name The role name specifier.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasRole = function hasRole (name) {
  if (!this.clientId) {
    return false;
  }

  const parts = name.split(':');
  if (parts.length === 1) {
    return this.hasApplicationRole(this.clientId, parts[0]);
  }

  if (parts[0] === 'realm') {
    return this.hasRealmRole(parts[1]);
  }

  return this.hasApplicationRole(parts[0], parts[1]);
};

/**
 * Determine if this token has an associated specific application role.
 *
 * Even if `clientId` is not set, this method may be used to explicitly test
 * roles for any given application.
 *
 * @param {String} appName The identifier of the application to test.
 * @param {String} roleName The name of the role within that application to test.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasApplicationRole = function hasApplicationRole (appName, roleName) {
  var appRoles = this.content.resource_access[appName];

  if (!appRoles) {
    return false;
  }

  return (appRoles.roles.indexOf(roleName) >= 0);
};

/**
 * Determine if this token has an associated specific realm-level role.
 *
 * Even if `clientId` is not set, this method may be used to explicitly test
 * roles for the realm.
 *
 * @param {String} appName The identifier of the application to test.
 * @param {String} roleName The name of the role within that application to test.
 *
 * @return {boolean} `true` if this token has the specified role, otherwise `false`.
 */
Token.prototype.hasRealmRole = function hasRealmRole (roleName) {
  return (this.content.realm_access.roles.indexOf(roleName) >= 0);
};

module.exports = Token;
<|endoftext|>


<commit_before>'use strict';

module.exports = function(sequelize, DataTypes){

    var JOB_REQUEST = sequelize.define('job_request', {
          DATE   : DataTypes.DATE
        , EMAIL  : {
            type : DataTypes.STRING
        , allowNull : true
        , validate  : {
            isEmail   : true
        }
    }
        , DESCRIPTION : DataTypes.TEXT
    }, {
        classMethods  : {
            associate : function(models){
                JOB_REQUEST.belongsTo(models.job);
                JOB_REQUEST.belongsTo(models.user);
            }
      }
    });

    return JOB_REQUEST;
};
<commit_msg>added sequelize date now<commit_after>'use strict';

module.exports = function(sequelize, DataTypes){

    var JOB_REQUEST = sequelize.define('job_request', {
          DATE            : {
             type         : DataTypes.DATE
           , defaultValue : sequelize.NOW
        }
        , EMAIL  : {
            type : DataTypes.STRING
        , allowNull   : true
        , validate    : {
            isEmail   : true
        }
    }
        , DESCRIPTION : DataTypes.TEXT
    }, {
        classMethods  : {
            associate : function(models){
                JOB_REQUEST.belongsTo(models.job);
                JOB_REQUEST.belongsTo(models.user);
            }
      }
    });

    return JOB_REQUEST;
};
<|endoftext|>


<commit_before>'use strict';

(function() {

    angular
        .module('simpleArmoryApp')
        .controller('AdminCtrl', AdminCtrl);

    function AdminCtrl($scope, AdminService, $routeParams, $window) {

        // Analytics for page
        $window.ga('send', 'pageview', 'Admin');

        // TODO: change download url to be based on file we're working on
        $scope.downloadFile = 'mounts.json';

        // TODO: don't show save button unless we've made a change
        // TODO: service and stuff should be based on changes I made

        AdminService.getMountData().then(function(data){
            // store the data in the scope so that we can build out forms from it
            var categories = [];
            for(var i=0; i<data.length; i++) {
                var cat = data[i];
                var name = cat.name;
                categories.push(cat);
            }

            $scope.categories = categories;
            $scope.selectedCat = $scope.categories[0];

            selectionChanged();
        });

        $scope.saveClicked = function() {
            // NOTE: There is probably an easier way todo this, but I'm using 2 anchors, one to trigger refresh of data
            // and a 2nd to actually download that data

            // trigger hidden link
            var anchor = document.getElementById('downloadLink');
            anchor.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(angular.toJson($scope.categories, 2));
            anchor.click()
        }

        function selectionChanged() {
            $scope.upButtonDisabled = $scope.categories.indexOf($scope.selectedCat) == 0;
            $scope.downButtonDisabled = $scope.categories.indexOf($scope.selectedCat) == $scope.categories.length - 1;

            $scope.selectedSubCat = $scope.selectedCat.subcats[0];
        }

        /* ## Category ############################################################################### */

        $scope.addCategory = function() {
            var newCategory = prompt('Category to add:');
            if (newCategory != '') {
                var catObj = { name: newCategory, subcats: [] }
                $scope.categories.push(catObj);
            }
        }

        $scope.removeCategory = function() {
            $scope.categories = $scope.categories.filter(function(category){
                return category != $scope.selectedCat;
            });

            $scope.selectedCat = $scope.categories[0];

            selectionChanged();
        }

        $scope.moveCategoryUp = function() {
            moveCategory(true);
        }

        $scope.moveCategoryDown = function() {
            moveCategory(false);
        }

        function moveCategory(up) {
            var catToMove = $scope.selectedCat;
            var src = $scope.categories.indexOf(catToMove);
            var dest = up ? src - 1 : src + 1;

            $scope.categories[src] = $scope.categories[dest];
            $scope.categories[dest] = catToMove;

            selectionChanged();
        }

        $scope.catChanged = function() {
            selectionChanged();
        }

        /* ## Sub Category ############################################################################### */

        $scope.removeSubCategory = function() {
            $scope.selectedCat.subcats = $scope.selectedCat.subcats.filter(function(sub){
                return sub != $scope.selectedSubCat;
            });

            selectionChanged();
        }

        $scope.addSubCategory = function() {
            // var newCat = prompt('Sub Category to add:');
            // if (newCat != '') {
            //     var catObj = { name: newCat, items: [] };
            //     $scope.mountSubCategories.push(catObj);
            // }
        }
    }

})();<commit_msg>Add sub category<commit_after>'use strict';

(function() {

    angular
        .module('simpleArmoryApp')
        .controller('AdminCtrl', AdminCtrl);

    function AdminCtrl($scope, AdminService, $routeParams, $window) {

        // Analytics for page
        $window.ga('send', 'pageview', 'Admin');

        // TODO: change download url to be based on file we're working on
        $scope.downloadFile = 'mounts.json';

        // TODO: don't show save button unless we've made a change
        // TODO: service and stuff should be based on changes I made

        AdminService.getMountData().then(function(data){
            // store the data in the scope so that we can build out forms from it
            var categories = [];
            for(var i=0; i<data.length; i++) {
                var cat = data[i];
                var name = cat.name;
                categories.push(cat);
            }

            $scope.categories = categories;
            $scope.selectedCat = $scope.categories[0];

            selectionChanged();
        });

        $scope.saveClicked = function() {
            // NOTE: There is probably an easier way todo this, but I'm using 2 anchors, one to trigger refresh of data
            // and a 2nd to actually download that data

            // trigger hidden link
            var anchor = document.getElementById('downloadLink');
            anchor.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(angular.toJson($scope.categories, 2));
            anchor.click()
        }

        function selectionChanged() {
            $scope.upButtonDisabled = $scope.categories.indexOf($scope.selectedCat) == 0;
            $scope.downButtonDisabled = $scope.categories.indexOf($scope.selectedCat) == $scope.categories.length - 1;

            $scope.selectedSubCat = $scope.selectedCat.subcats[0];
        }

        /* ## Category ############################################################################### */

        $scope.addCategory = function() {
            var newCategory = prompt('Category to add:');
            if (newCategory != '') {
                var catObj = { name: newCategory, subcats: [] }
                $scope.categories.push(catObj);
            }

            selectionChanged();
        }

        $scope.removeCategory = function() {
            $scope.categories = $scope.categories.filter(function(category){
                return category != $scope.selectedCat;
            });

            $scope.selectedCat = $scope.categories[0];

            selectionChanged();
        }

        $scope.moveCategoryUp = function() {
            moveCategory(true);
        }

        $scope.moveCategoryDown = function() {
            moveCategory(false);
        }

        function moveCategory(up) {
            var catToMove = $scope.selectedCat;
            var src = $scope.categories.indexOf(catToMove);
            var dest = up ? src - 1 : src + 1;

            $scope.categories[src] = $scope.categories[dest];
            $scope.categories[dest] = catToMove;

            selectionChanged();
        }

        $scope.catChanged = function() {
            selectionChanged();
        }

        /* ## Sub Category ############################################################################### */

        $scope.removeSubCategory = function() {
            $scope.selectedCat.subcats = $scope.selectedCat.subcats.filter(function(sub){
                return sub != $scope.selectedSubCat;
            });

            selectionChanged();
        }

        $scope.addSubCategory = function() {
             var newCat = prompt('Sub Category to add:');
             if (newCat != '') {
                 var catObj = { name: newCat, items: [] };
                 $scope.selectedCat.subcats.push(catObj);
             }

             selectionChanged();
        }
    }

})();<|endoftext|>


<commit_before>import $ from "jquery";

module.exports = function(originalText, opinionated) {
  if (typeof opinionated == "undefined") {
    // The default criteria is opinionated: if there's no difference between
    // leaving a word on either line, it's left on the lower one.
    opinionated = true;
  }

  let text = originalText.replace(/[\n\r]/g, " "); // Delete line breaks, we'll do those
  text = text.replace(/\s\s+/, " "); // Remove multiple consecutive spaces, not relevant

  if (text.length > 80) {
    // We cannot do anything with this, it's beyond max
    return [originalText];
  }

  let dialogLineCount = (text.match(/(?:^|\s)-/g) || []).length;
  let isDialog = text.match(/^\s*-/g) && dialogLineCount == 2;
  if (isDialog) {
    /*
     * Looks like we have a dialog. We must preserve each line separated unless that is not possible.
     */
    let fDialogPos = text.indexOf("-");
    let fDialogPos2 = text.substr(fDialogPos + 1).indexOf(" -") + fDialogPos;
    if (fDialogPos2 - fDialogPos < 40 && text.length - 2 - fDialogPos2 <= 40) {
      //len - 1 due to the space introduced by the line break
      // If they fit in two separate lines, that's how it goes
      let dialogLines = [];
      dialogLines[0] = text.slice(0, fDialogPos2 + 1).trim();
      dialogLines[1] = text.slice(fDialogPos2 + 1, text.length).trim();
      return dialogLines;
    }
  }

  if (text.length <= 40) {
    // Nothing to divide
    return [originalText];
  }

  let behind = 0;
  let ahead = text.length;
  let curWordPos = 0;
  let ignoreSepUntilNextWord = false;
  for (let i = 0; i < text.length; ++i) {
    behind++;
    ahead--;

    let curChar = text[i];
    if (curChar == "-") {
      ignoreSepUntilNextWord = true;
      continue;
    }

    // If we find a word separator...
    if (curChar.match(/[ .,;?!\-¿¡"']/)) {
      if (ignoreSepUntilNextWord) continue;

      let nextChar = i + 1 < text.length ? text[i + 1] : null;
      let prevChar = i > 0 ? text[i - 1] : null;

      /*
       * If next char is also a separator (unless its a space), we do not split yet.
       */
      if (
        nextChar &&
        (nextChar.match(/[.,;?!\-]/) || (curChar != " " && nextChar.match(/[¿¡"']/)))
      ) {
        continue;
      }

      /**
       * If next char is alphanumeric (/ accented vocal) or yet another separator,
       * and curChar is an opening separator, continue
       */
      if (nextChar && nextChar.match(/[\w¿¡"'àèìòùáéíóúâêîôû]/) && curChar.match(/[¿¡"']/)) {
        continue;
      }

      /*
       * Numbers, format: 123.578.213
       * If we're at a dot or space, look forward and backwards. If there's a number, and the
       * next character also is a number, keep consuming characters.
       */
      if (
        (curChar == "." || curChar == " ") &&
        nextChar &&
        prevChar &&
        $.isNumeric(nextChar) &&
        $.isNumeric(prevChar)
      ) {
        continue;
      }

      if (behind >= ahead) {
        // Begin splitting
        if (curChar == " ") {
          // Move one back, as the space isn't part of the word!
          --i;
          --behind;
          ++ahead;
        }

        // <dir>IfSplit counts the number of characters that we'd actually have if we ignore spaces
        let aheadIfSplit = ahead;
        if (aheadIfSplit > 0) {
          let j = i + 1;
          while (j < text.length) {
            if (text[j] != " ") {
              j--;
              continue;
            }

            aheadIfSplit--;
            j++;
            break;
          }
        }

        let behindIfSplit = behind;
        if (behindIfSplit > 0) {
          let j = i - 1;
          while (j >= 0) {
            if (text[j] != " ") {
              j--;
              continue;
            }

            behindIfSplit--;
            break;
          }
        }

        // Split
        let curWordSize = i - curWordPos;
        let aheadWithWord = aheadIfSplit + curWordSize;
        let behindWithoutWord = behindIfSplit - curWordSize;
        let splitAt = i;

        let diff =
          Math.abs(aheadWithWord - behindWithoutWord) - Math.abs(aheadIfSplit - behindIfSplit);
        if (diff < 0 || (opinionated && diff == 0)) {
          // Should've split on the last separator
          splitAt = curWordPos;
        }

        let lines = [];
        lines[0] = text.slice(0, splitAt + 1).trim();
        lines[1] = text.slice(splitAt + 1, text.length).trim();
        return lines;
      }

      curWordPos = i;
    } else if (ignoreSepUntilNextWord) {
      // We found a char that's not a separator so remove the flag and carry on
      ignoreSepUntilNextWord = false;
    }
  }

  // No division
  return [text];
};
<commit_msg>Split quotes with their text when there's a linebreak inbetween<commit_after>import $ from "jquery";

module.exports = function(originalText, opinionated) {
  if (typeof opinionated == "undefined") {
    // The default criteria is opinionated: if there's no difference between
    // leaving a word on either line, it's left on the lower one.
    opinionated = true;
  }

  let text = originalText.replace(/\s(["'])[\n\r]([\w¿¡ñáéíóú])/g, " $1$2"); // Remove linebreaks between "/' and text.
  text = text.replace(/[\n\r]/g, " "); // Delete line breaks, we'll do those
  text = text.replace(/\s\s+/, " "); // Remove multiple consecutive spaces, not relevant

  if (text.length > 80) {
    // We cannot do anything with this, it's beyond max
    return [originalText];
  }

  let dialogLineCount = (text.match(/(?:^|\s)-/g) || []).length;
  let isDialog = text.match(/^\s*-/g) && dialogLineCount == 2;
  if (isDialog) {
    /*
     * Looks like we have a dialog. We must preserve each line separated unless that is not possible.
     */
    let fDialogPos = text.indexOf("-");
    let fDialogPos2 = text.substr(fDialogPos + 1).indexOf(" -") + fDialogPos;
    if (fDialogPos2 - fDialogPos < 40 && text.length - 2 - fDialogPos2 <= 40) {
      //len - 1 due to the space introduced by the line break
      // If they fit in two separate lines, that's how it goes
      let dialogLines = [];
      dialogLines[0] = text.slice(0, fDialogPos2 + 1).trim();
      dialogLines[1] = text.slice(fDialogPos2 + 1, text.length).trim();
      return dialogLines;
    }
  }

  if (text.length <= 40) {
    // Nothing to divide
    return [originalText];
  }

  let behind = 0;
  let ahead = text.length;
  let curWordPos = 0;
  let ignoreSepUntilNextWord = false;
  for (let i = 0; i < text.length; ++i) {
    behind++;
    ahead--;

    let curChar = text[i];
    if (curChar == "-") {
      ignoreSepUntilNextWord = true;
      continue;
    }

    // If we find a word separator...
    if (curChar.match(/[ .,;?!\-¿¡"']/)) {
      if (ignoreSepUntilNextWord) continue;

      let nextChar = i + 1 < text.length ? text[i + 1] : null;
      let prevChar = i > 0 ? text[i - 1] : null;

      /*
       * If next char is also a separator (unless its a space), we do not split yet.
       */
      if (
        nextChar &&
        (nextChar.match(/[.,;?!\-]/) || (curChar != " " && nextChar.match(/[¿¡"']/)))
      ) {
        continue;
      }

      /**
       * If next char is alphanumeric (/ accented vocal) or yet another separator,
       * and curChar is an opening separator, continue
       */
      if (nextChar && nextChar.match(/[\w¿¡"'àèìòùáéíóúâêîôû]/) && curChar.match(/[¿¡"']/)) {
        continue;
      }

      /*
       * Numbers, format: 123.578.213
       * If we're at a dot or space, look forward and backwards. If there's a number, and the
       * next character also is a number, keep consuming characters.
       */
      if (
        (curChar == "." || curChar == " ") &&
        nextChar &&
        prevChar &&
        $.isNumeric(nextChar) &&
        $.isNumeric(prevChar)
      ) {
        continue;
      }

      if (behind >= ahead) {
        // Begin splitting
        if (curChar == " ") {
          // Move one back, as the space isn't part of the word!
          --i;
          --behind;
          ++ahead;
        }

        // <dir>IfSplit counts the number of characters that we'd actually have if we ignore spaces
        let aheadIfSplit = ahead;
        if (aheadIfSplit > 0) {
          let j = i + 1;
          while (j < text.length) {
            if (text[j] != " ") {
              j--;
              continue;
            }

            aheadIfSplit--;
            j++;
            break;
          }
        }

        let behindIfSplit = behind;
        if (behindIfSplit > 0) {
          let j = i - 1;
          while (j >= 0) {
            if (text[j] != " ") {
              j--;
              continue;
            }

            behindIfSplit--;
            break;
          }
        }

        // Split
        let curWordSize = i - curWordPos;
        let aheadWithWord = aheadIfSplit + curWordSize;
        let behindWithoutWord = behindIfSplit - curWordSize;
        let splitAt = i;

        let diff =
          Math.abs(aheadWithWord - behindWithoutWord) - Math.abs(aheadIfSplit - behindIfSplit);
        if (diff < 0 || (opinionated && diff == 0)) {
          // Should've split on the last separator
          splitAt = curWordPos;
        }

        let lines = [];
        lines[0] = text.slice(0, splitAt + 1).trim();
        lines[1] = text.slice(splitAt + 1, text.length).trim();
        return lines;
      }

      curWordPos = i;
    } else if (ignoreSepUntilNextWord) {
      // We found a char that's not a separator so remove the flag and carry on
      ignoreSepUntilNextWord = false;
    }
  }

  // No division
  return [text];
};
<|endoftext|>