<commit_before>import base64ArrayBuffer from '../lib/base64'

const CCOperation = ['kCCEncrypt', 'kCCDecrypt']
const CCAlgorithm = [
  { name: 'kCCAlgorithmAES128', blocksize: 16 },
  { name: 'kCCAlgorithmDES', blocksize: 8 },
  { name: 'kCCAlgorithm3DES', blocksize: 8 },
  { name: 'kCCAlgorithmCAST', blocksize: 8 },
  { name: 'kCCAlgorithmRC4', blocksize: 8 },
  { name: 'kCCAlgorithmRC2', blocksize: 8 },
]

const subject = 'crypto'
const now = () => (new Date()).getTime()

// CCCryptorStatus
// CCCryptorCreate(CCOperation op, CCAlgorithm alg, CCOptions options,
//     const void *key, size_t keyLength, const void *iv,
//     CCCryptorRef *cryptorRef);

Interceptor.attach(Module.findExportByName(null, 'CCCryptorCreate'), {
  onEnter(args) {
    const op = args[0].toInt32()
    const alg = args[1].toInt32()
    // const options = args[2].toInt32()
    const key = args[3]
    const keyLength = args[4].toInt32()
    const iv = args[5]

    const strKey = base64ArrayBuffer(Memory.readByteArray(key, keyLength))
    const strIV = iv === 0 ? 'null' : base64ArrayBuffer(Memory.readByteArray(iv, CCAlgorithm[alg].blocksize))

    const time = now()
    const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
      .map(DebugSymbol.fromAddress).filter(e => e.name)

    let operation = CCOperation[op]
    if (operation === 'kCCEncrypt')
      operation = 'encrypt'
    else if (operation === 'kCCDecrypt')
      operation = 'decrypt'
    else
      console.error('unknown operation', op)

    send({
      subject,
      func: 'CCCryptorCreate',
      event: operation,
      arguments: {
        operation,
        algorithm: CCAlgorithm[alg].name,
        key: strKey,
        iv: strIV,
      },
      time,
      backtrace,
    })
  },
})

// CCCryptorStatus
// CCCrypt(CCOperation op, CCAlgorithm alg, CCOptions options,
//     const void *key, size_t keyLength, const void *iv,
//     const void *dataIn, size_t dataInLength, void *dataOut,
//     size_t dataOutAvailable, size_t *dataOutMoved);

Interceptor.attach(Module.findExportByName(null, 'CCCrypt'), {
  onEnter(args) {
    const op = args[0].toInt32()
    const alg = args[1].toInt32()
    // const options = args[2].toInt32()
    const key = args[3]
    const keyLength = args[4].toInt32()
    const iv = args[5]
    const dataIn = args[6]
    const dataInLength = args[7].toInt32()
    const dataOut = args[8]
    const dataOutAvailable = args[9]
    const dataOutMoved = args[10]

    this.dataOut = dataOut
    this.dataOutAvailable = dataOutAvailable
    this.dataOutMoved = dataOutMoved

    const strKey = base64ArrayBuffer(Memory.readByteArray(key, keyLength))
    const strIV = iv === 0 ? 'null' : base64ArrayBuffer(Memory.readByteArray(iv, CCAlgorithm[alg].blocksize))

    const strDataIn = base64ArrayBuffer(Memory.readByteArray(dataIn, dataInLength))

    const time = now()
    const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
      .map(DebugSymbol.fromAddress).filter(e => e.name)

    let operation = CCOperation[op]
    if (operation === 'kCCEncrypt')
      operation = 'encrypt'
    else if (operation === 'kCCDecrypt')
      operation = 'decrypt'
    else
      console.error('unknown operation', op)

    this.operation = operation
    send({
      subject,
      event: operation,
      arguments: {
        operation,
        algorithm: CCAlgorithm[alg].name,
        key: strKey,
        iv: strIV,
        in: strDataIn,
      },
      time,
      backtrace,
    })
  },
  onLeave(retVal) {
    if (retVal.toInt32() !== 0)
      return

    const time = now()
    const { dataOut, dataOutMoved, operation } = this
    const len = Memory.readUInt(dataOutMoved)
    const strDataOut = base64ArrayBuffer(Memory.readByteArray(dataOut, len))

    send({
      subject,
      event: operation,
      arguments: {
        out: strDataOut,
      },
      time,
    })
  },
})
<commit_msg>disable ccrypt hooks by default<commit_after>import base64ArrayBuffer from '../lib/base64'

const CCOperation = ['kCCEncrypt', 'kCCDecrypt']
const CCAlgorithm = [
  { name: 'kCCAlgorithmAES128', blocksize: 16 },
  { name: 'kCCAlgorithmDES', blocksize: 8 },
  { name: 'kCCAlgorithm3DES', blocksize: 8 },
  { name: 'kCCAlgorithmCAST', blocksize: 8 },
  { name: 'kCCAlgorithmRC4', blocksize: 8 },
  { name: 'kCCAlgorithmRC2', blocksize: 8 },
]

const subject = 'crypto'
const now = () => (new Date()).getTime()

const handlers = {
  // CCCryptorStatus
  // CCCryptorCreate(CCOperation op, CCAlgorithm alg, CCOptions options,
  //     const void *key, size_t keyLength, const void *iv,
  //     CCCryptorRef *cryptorRef);

  CCCryptorCreate: {
    onEnter(args) {
      const op = args[0].toInt32()
      const alg = args[1].toInt32()
      // const options = args[2].toInt32()
      const key = args[3]
      const keyLength = args[4].toInt32()
      const iv = args[5]

      const strKey = base64ArrayBuffer(Memory.readByteArray(key, keyLength))
      const strIV = iv === 0 ? 'null' : base64ArrayBuffer(Memory.readByteArray(iv, CCAlgorithm[alg].blocksize))

      const time = now()
      const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).filter(e => e.name)

      let operation = CCOperation[op]
      if (operation === 'kCCEncrypt')
        operation = 'encrypt'
      else if (operation === 'kCCDecrypt')
        operation = 'decrypt'
      else
        console.error('unknown operation', op)

      send({
        subject,
        func: 'CCCryptorCreate',
        event: operation,
        arguments: {
          operation,
          algorithm: CCAlgorithm[alg].name,
          key: strKey,
          iv: strIV,
        },
        time,
        backtrace,
      })
    },
  },

  // CCCryptorStatus
  // CCCrypt(CCOperation op, CCAlgorithm alg, CCOptions options,
  //     const void *key, size_t keyLength, const void *iv,
  //     const void *dataIn, size_t dataInLength, void *dataOut,
  //     size_t dataOutAvailable, size_t *dataOutMoved);

  CCCrypt: {
    onEnter(args) {
      const op = args[0].toInt32()
      const alg = args[1].toInt32()
      // const options = args[2].toInt32()
      const key = args[3]
      const keyLength = args[4].toInt32()
      const iv = args[5]
      const dataIn = args[6]
      const dataInLength = args[7].toInt32()
      const dataOut = args[8]
      const dataOutAvailable = args[9]
      const dataOutMoved = args[10]

      this.dataOut = dataOut
      this.dataOutAvailable = dataOutAvailable
      this.dataOutMoved = dataOutMoved

      const strKey = base64ArrayBuffer(Memory.readByteArray(key, keyLength))
      const strIV = iv === 0 ? 'null' : base64ArrayBuffer(Memory.readByteArray(iv, CCAlgorithm[alg].blocksize))

      const strDataIn = base64ArrayBuffer(Memory.readByteArray(dataIn, dataInLength))

      const time = now()
      const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).filter(e => e.name)

      let operation = CCOperation[op]
      if (operation === 'kCCEncrypt')
        operation = 'encrypt'
      else if (operation === 'kCCDecrypt')
        operation = 'decrypt'
      else
        console.error('unknown operation', op)

      this.operation = operation
      send({
        subject,
        event: operation,
        arguments: {
          operation,
          algorithm: CCAlgorithm[alg].name,
          key: strKey,
          iv: strIV,
          in: strDataIn,
        },
        time,
        backtrace,
      })
    },
    onLeave(retVal) {
      if (retVal.toInt32() !== 0)
        return

      const time = now()
      const { dataOut, dataOutMoved, operation } = this
      const len = Memory.readUInt(dataOutMoved)
      const strDataOut = base64ArrayBuffer(Memory.readByteArray(dataOut, len))

      send({
        subject,
        event: operation,
        arguments: {
          out: strDataOut,
        },
        time,
      })
    },
  },
}

let hooks = []
export default function toggle(on) {
  if (on && !hooks.length)
    for (const func in handlers)
      if (({}).hasOwnProperty.call(handlers, func))
        hooks.push(Interceptor.attach(Module.findExportByName(null, func), handlers[func]))

  if (!on && hooks.length) {
    hooks.forEach(hook => hook.detach())
    hooks = []
  }
}
<|endoftext|>


<commit_before>/*
 *    Minimum Steps to One
 *
 *     Given a positive integer, you can perform any combination of these 3 steps:
 *      1.) Subtract 1 from it.
 *      2.) If its divisible by 2, divide by 2.
 *      3.) If its divisible by 3, divide by 3.
 *
 *     Find the minimum number of steps that it takes get from N to 1
 *
 *  Input: Positive Integer N
 *  Output:Integer
 */

function minStepsToOne(n, steps=0){
  if( n === 1 ) return steps;

  if( n % 3 === 0 ) return minStepsToOne(n/3, steps+=1)
  if( n % 2 === 0 ) return minStepsToOne(n/2, steps+=1)
  return minStepsToOne(n-=1, steps+=1)
}

console.log(minStepsToOne(11))
<commit_msg>add solution<commit_after>/*
 *    Minimum Steps to One
 *
 *     Given a positive integer, you can perform any combination of these 3 steps:
 *      1.) Subtract 1 from it.
 *      2.) If its divisible by 2, divide by 2.
 *      3.) If its divisible by 3, divide by 3.
 *
 *     Find the minimum number of steps that it takes get from N to 1
 *
 *  Input: Positive Integer N
 *  Output:Integer
 */

// solved using pure recurion w/ side effects
// set base case if n = 1, return amount of steps we took

// if n is divisible by 3 divide and increase steps by 1
// if n is divisible by 2 divide and increase steps by 1
// if n isnt divisible by 2 or 3 subtract by 1

function minStepsToOne(n, steps=0){
  if( n === 1 ) return steps;

  if( n % 3 === 0 ) return minStepsToOne(n/3, steps+=1)
  if( n % 2 === 0 ) return minStepsToOne(n/2, steps+=1)
  return minStepsToOne(n-=1, steps+=1)
}

console.log(minStepsToOne(11))
<|endoftext|>


<commit_before>'use strict';

//
//  Yeoman bat:model sub-generator.
//

var generators      = require( 'yeoman-generator' )
,   yosay           = require( 'yosay' )
,   youtil          = require( './../../lib/youtil.js' )
,   chalk           = require( 'chalk' )
,   glob            = require( 'glob' )
,   _               = require( 'lodash' )
;

var ModelGenerator = generators.Base.extend(
    {
        constructor: function ()
        {
            generators.Base.apply( this, arguments );

            this.description    = this._description( 'backbone model' );

            this.argument(
                'modelName'
            ,   {
                    type:           String
                ,   required:       false
                ,   desc:           'The name of the model to create.'
                }
            );

            //  Also add 'modelName' as a - hidden - option, defaulting to the positional argument's value.
            //  This way `_promptsPruneByOptions()` can filter away prompting for the model name too.
            //
            this.option(
                'modelName'
            ,   {
                    type:           String
                ,   desc:           'The name of the model to create.'
                ,   default:        this.modelName
                ,   hide:           true
                }
            );

            //  Normal options.
            //
            this.option(
                'description'
            ,   {
                    type:           String
                ,   desc:           'The purpose of this model.'
                }
            );

            this.option(
                'api'
            ,   {
                    type:           String
                ,   desc:           'The name of the API this model should connect to.'
                }
            );

            this.option(
                'service'
            ,   {
                    type:           String
                ,   desc:           'The service API endpoint URL this model should connect to (relative to the API\'s base).'
                }
            );

            this.option(
                'singleton'
            ,   {
                    type:           Boolean
                ,   desc:           'Whether this model should be a singleton (instance).'
                }
            );
        }

    ,   initializing: function ()
        {
            this._assertBatApp();

            //  Find available APIs:
            //
            var apis    = this.apis
                        = {}
            ,   base    = this.destinationPath( 'src/apis' )
            ;

            glob.sync( '**/*.coffee', { cwd: base } ).forEach(

                function ( path )
                {
                    var pathAbs     = base + '/' + path;
                    var match       = this.fs.read( pathAbs ).match( /@class\s+(\S+)/ );

                    if ( !( match ) ) { return; }

                    var className   = match[ 1 ]
                    ,   name        = _.lowerFirst( className.replace( /Api$/, '' ))
                    ;

                    apis[ name ]   =
                        {
                            className:  className
                        ,   pathAbs:    pathAbs
                        ,   path:       path
                        }
                    ;

                }.bind( this )
            );

            //  Container for template expansion data.
            //
            this.templateData   = {};
        }

    ,   prompting:
        {
            askSomeQuestions: function ()
            {
                //  Ask only those question that have not yet been provided with answers via the command line.
                //
                var prompts = this._promptsPruneByOptions(
                        [
                            {
                                type:       'input'
                            ,   name:       'modelName'
                            ,   message:    'What is the name of this model you so desire?'
                            ,   default:    youtil.definedToString( this.options.modelName )
                            ,   validate:   youtil.isIdentifier
                            ,   filter: function ( value )
                                {
                                    return _.lowerFirst( _.trim( value ).replace( /model$/i, '' ));
                                }
                            }
                        ,   {
                                type:       'input'
                            ,   name:       'description'
                            ,   message:    'What is the purpose (description) of this model?'
                            ,   default:    youtil.definedToString( this.options.description )
                            ,   validate:   youtil.isNonBlank
                            ,   filter:     youtil.sentencify
                            }
                        ,   {
                                type:       'list'
                            ,   name:       'api'
                            ,   message:    'Should this model connect to an API?'
                            ,   choices:    [ '- none -' ].concat( _.keys( this.apis ))
                            ,   default:    youtil.definedToString( this.options.api )
                            ,   validate: function ( value )
                                {
                                    return value in this.apis;
                                }.bind( this )
                            ,   filter: function ( value ) {
                                    return this.apis[ value ];
                                }.bind( this )
                            ,   when:       !( _.isEmpty( this.apis ))
                            }
                        ,   {
                                type:       'input'
                            ,   name:       'service'
                            ,   message:    (
                                                'To which service API endpoint should this model connect?'
                                            +   chalk.gray ( ' - please enter a URL relative to the API\'s base.' )
                                            )
                            ,   default: function ( answers )
                                {
                                    return (
                                        youtil.definedToString( this.options.service )
                                    ||  answers.modelName
                                    ||  this.templateData.modelName
                                    );
                                }.bind( this )
                            ,   filter: function ( value )
                                {
                                    return value.replace( /^\/+/, '' );
                                }
                            ,   when: function ( answers )
                                {
                                    return answers.api || this.templateData.api;
                                }.bind( this )
                            }
                        ,   {
                                type:       'confirm'
                            ,   name:       'singleton'
                            ,   message:    'Should this model be a singleton (instance)?'
                            ,   default:    false
                            ,   validate:   _.isBoolean
                            }
                        ]
                    )
                ;

                if ( prompts.length )
                {
                    //  Have Yeoman greet the user.
                    //
                    this.log( yosay( 'So you want a BAT model?' ) );

                    return (
                        this
                            .prompt( prompts )
                            .then( function ( answers ) { _.extend( this.templateData, answers ); }.bind( this ) )
                    );
                }
            }
        }

    ,   configuring: function ()
        {
            var data            = this.templateData
            ,   modelName       = data.modelName
            ;

            _.extend(
                data
            ,   {
                    className:          _.upperFirst( modelName ) + 'Model'
                ,   fileBase:           _.kebabCase( _.deburr( modelName ))

                ,   userName:           this.user.git.name()

                ,   backbone:           ( this.config.get( 'backbone' ) || { className: 'Backbone', modulePath: 'backbone' } )
                }
            );
        }

    ,   writing:
        {
            createModel: function ()
            {
                var data        = this.templateData
                ,   templates   =
                    {
                        'model.coffee': [ 'src/models/' + data.fileBase + '.coffee' ]
                    }
                ;

                this._templatesProcess( templates );
            }
        }

    ,   install: {

            updateApi: function () {

                var data        = this.templateData
                ,   api         = data.api
                ,   modelName   = data.modelName
                ;

                if ( !( api )) { return; }

                //
                //  Insert the expanded fragment template into the api collection definition.
                //  Look for a place to insert, preferably at an alfanumerically ordered position.
                //  Do nothing if an service API endpoint defintion for this model seems to exist already.
                //

                var fs          = this.fs
                ,   collection  = fs.read( api.pathAbs )
                ,   matcherDec  = /^([ \t]*).*?\bnew\s+ApiServicesCollection\(\s*?(^[ \t]*)?\[[ \t]*(\n)?/m
                //                  1------1                                      2-------2         3--3
                ,   match       = collection.match( matcherDec )
                ;

                if ( !( match ) )
                {
                    this.log(
                        'It appears that "' + api.pathAbs + '" does not contains an `ApiServicesCollection`\n'
                    +   'Leaving it untouched.'
                    );

                    return;
                }

                var level       = '    '
                ,   indent      = (( match[ 2 ] != null ) ? match[ 2 ] : ( match[ 1 ] + level ))
                ,   insertAt    = match.index + match[ 0 ].length
                ,   padPre      = match[ 3 ] ? '' : '\n'
                ,   padPost     = match[ 3 ] ? '' : indent
                ,   matcherDef  = /^(([ \t]*)([ \t]+))###\*[\s\S]*?^\1###[\s\S]*?^\1id:\s*'([^\]]*?)'[^\]]*?^\2(?:,[ \t]*(\n)?|(?=(\])))/mg
                //                 ^12======23======31             ^\1           ^\1       4-------4        ^\2          5--5     6--6
                ;

                //  Start looking for definitions directly after API declaration opening.
                //
                matcherDef.lastIndex    = insertAt;

                //  Find a place to insert
                //
                while ( (( match = matcherDef.exec( collection ) )) )
                {
                    level   = match[ 3 ];

                    if ( modelName > match[ 4 ] )
                    {
                        //  Possibly insert after this match.
                        //
                        indent      = match[ 2 ];
                        insertAt    = match.index + match[ 0 ].length;
                        padPre      = match[ 5 ] ? '' : match[ 6 ] ? ',\n'  : '\n';
                        padPost     = match[ 5 ] ? '' : indent;
                        continue;
                    }

                    if ( modelName < match[ 4 ] )
                    {
                        //  Insert before this match.
                        //
                        insertAt    = match.index;
                        padPre      = '';
                        padPost     = '';
                        break;
                    }

                    this.log(
                        'It appears that "' + api.pathAbs + '" already contains a service API endpoint definition for "' + data.className + '".\n'
                    +   'Leaving it untouched.'
                    );

                    return;
                }

                //  Avoid conflict warning.
                //
                this.conflicter.force   = true;

                //  Expand fragment template and read it back.
                //
                var fragmentPath        = 'src/apis/api-service-literal-fragment.coffee'
                ,   fragmentDst         = this.destinationPath( fragmentPath )
                ;

                this._templatesProcess( [ [ fragmentPath ] ] );

                var fragment            = fs.read( fragmentDst );

                fs.write(
                    api.pathAbs
                ,   collection.slice( 0, insertAt )
                +   padPre
                +   fragment.replace( /^    /mg, level ).replace( /^(?=.*?\S)/mg, indent )
                +   padPost
                +   collection.slice( insertAt )
                );

                fs.delete( fragmentDst );
            }
        }
    }
);

_.extend(
    ModelGenerator.prototype
,   require( './../../lib/generator.js' )
,   require( './../../lib/sub-generator.js' )
);

module.exports = ModelGenerator;
<commit_msg>fix(generators/model): ensure default service-name validates as a url-path<commit_after>'use strict';

//
//  Yeoman bat:model sub-generator.
//

var generators      = require( 'yeoman-generator' )
,   yosay           = require( 'yosay' )
,   youtil          = require( './../../lib/youtil.js' )
,   chalk           = require( 'chalk' )
,   glob            = require( 'glob' )
,   url             = require( 'url' )                      //  https://nodejs.org/api/url.html
,   _               = require( 'lodash' )
;

var ModelGenerator = generators.Base.extend(
    {
        constructor: function ()
        {
            generators.Base.apply( this, arguments );

            this.description    = this._description( 'backbone model' );

            this.argument(
                'modelName'
            ,   {
                    type:           String
                ,   required:       false
                ,   desc:           'The name of the model to create.'
                }
            );

            //  Also add 'modelName' as a - hidden - option, defaulting to the positional argument's value.
            //  This way `_promptsPruneByOptions()` can filter away prompting for the model name too.
            //
            this.option(
                'modelName'
            ,   {
                    type:           String
                ,   desc:           'The name of the model to create.'
                ,   default:        this.modelName
                ,   hide:           true
                }
            );

            //  Normal options.
            //
            this.option(
                'description'
            ,   {
                    type:           String
                ,   desc:           'The purpose of this model.'
                }
            );

            this.option(
                'api'
            ,   {
                    type:           String
                ,   desc:           'The name of the API this model should connect to.'
                }
            );

            this.option(
                'service'
            ,   {
                    type:           String
                ,   desc:           'The service API endpoint URL this model should connect to (relative to the API\'s base).'
                }
            );

            this.option(
                'singleton'
            ,   {
                    type:           Boolean
                ,   desc:           'Whether this model should be a singleton (instance).'
                }
            );
        }

    ,   initializing: function ()
        {
            this._assertBatApp();

            //  Find available APIs:
            //
            var apis    = this.apis
                        = {}
            ,   base    = this.destinationPath( 'src/apis' )
            ;

            glob.sync( '**/*.coffee', { cwd: base } ).forEach(

                function ( path )
                {
                    var pathAbs     = base + '/' + path;
                    var match       = this.fs.read( pathAbs ).match( /@class\s+(\S+)/ );

                    if ( !( match ) ) { return; }

                    var className   = match[ 1 ]
                    ,   name        = _.lowerFirst( className.replace( /Api$/, '' ))
                    ;

                    apis[ name ]   =
                        {
                            className:  className
                        ,   pathAbs:    pathAbs
                        ,   path:       path
                        }
                    ;

                }.bind( this )
            );

            //  Container for template expansion data.
            //
            this.templateData   = {};
        }

    ,   prompting:
        {
            askSomeQuestions: function ()
            {
                //  Ask only those question that have not yet been provided with answers via the command line.
                //
                var prompts = this._promptsPruneByOptions(
                        [
                            {
                                type:       'input'
                            ,   name:       'modelName'
                            ,   message:    'What is the name of this model you so desire?'
                            ,   default:    youtil.definedToString( this.options.modelName )
                            ,   validate:   youtil.isIdentifier
                            ,   filter: function ( value )
                                {
                                    return _.lowerFirst( _.trim( value ).replace( /model$/i, '' ));
                                }
                            }
                        ,   {
                                type:       'input'
                            ,   name:       'description'
                            ,   message:    'What is the purpose (description) of this model?'
                            ,   default:    youtil.definedToString( this.options.description )
                            ,   validate:   youtil.isNonBlank
                            ,   filter:     youtil.sentencify
                            }
                        ,   {
                                type:       'list'
                            ,   name:       'api'
                            ,   message:    'Should this model connect to an API?'
                            ,   choices:    [ '- none -' ].concat( _.keys( this.apis ))
                            ,   default:    youtil.definedToString( this.options.api )
                            ,   validate: function ( value )
                                {
                                    return value in this.apis;
                                }.bind( this )
                            ,   filter: function ( value ) {
                                    return this.apis[ value ];
                                }.bind( this )
                            ,   when:       !( _.isEmpty( this.apis ))
                            }
                        ,   {
                                type:       'input'
                            ,   name:       'service'
                            ,   message:    (
                                                'To which service API endpoint should this model connect?'
                                            +   chalk.gray ( ' - please enter a URL relative to the API\'s base.' )
                                            )
                            ,   default: function ( answers )
                                {
                                    return (
                                        youtil.definedToString( this.options.service )
                                    ||  _.kebabCase( _.deburr(
                                            answers.modelName
                                        ||  this.templateData.modelName
                                        ))
                                    );
                                }.bind( this )
                            ,   validate: function( value ) {
                                    return value === url.parse( value ).path
                                }
                            ,   filter: function ( value )
                                {
                                    return value.replace( /^\/+/, '' );
                                }
                            ,   when: function ( answers )
                                {
                                    return answers.api || this.templateData.api;
                                }.bind( this )
                            }
                        ,   {
                                type:       'confirm'
                            ,   name:       'singleton'
                            ,   message:    'Should this model be a singleton (instance)?'
                            ,   default:    false
                            ,   validate:   _.isBoolean
                            }
                        ]
                    )
                ;

                if ( prompts.length )
                {
                    //  Have Yeoman greet the user.
                    //
                    this.log( yosay( 'So you want a BAT model?' ) );

                    return (
                        this
                            .prompt( prompts )
                            .then( function ( answers ) { _.extend( this.templateData, answers ); }.bind( this ) )
                    );
                }
            }
        }

    ,   configuring: function ()
        {
            var data            = this.templateData
            ,   modelName       = data.modelName
            ;

            _.extend(
                data
            ,   {
                    className:          _.upperFirst( modelName ) + 'Model'
                ,   fileBase:           _.kebabCase( _.deburr( modelName ))

                ,   userName:           this.user.git.name()

                ,   backbone:           ( this.config.get( 'backbone' ) || { className: 'Backbone', modulePath: 'backbone' } )
                }
            );
        }

    ,   writing:
        {
            createModel: function ()
            {
                var data        = this.templateData
                ,   templates   =
                    {
                        'model.coffee': [ 'src/models/' + data.fileBase + '.coffee' ]
                    }
                ;

                this._templatesProcess( templates );
            }
        }

    ,   install: {

            updateApi: function () {

                var data        = this.templateData
                ,   api         = data.api
                ,   modelName   = data.modelName
                ;

                if ( !( api )) { return; }

                //
                //  Insert the expanded fragment template into the api collection definition.
                //  Look for a place to insert, preferably at an alfanumerically ordered position.
                //  Do nothing if an service API endpoint defintion for this model seems to exist already.
                //

                var fs          = this.fs
                ,   collection  = fs.read( api.pathAbs )
                ,   matcherDec  = /^([ \t]*).*?\bnew\s+ApiServicesCollection\(\s*?(^[ \t]*)?\[[ \t]*(\n)?/m
                //                  1------1                                      2-------2         3--3
                ,   match       = collection.match( matcherDec )
                ;

                if ( !( match ) )
                {
                    this.log(
                        'It appears that "' + api.pathAbs + '" does not contains an `ApiServicesCollection`\n'
                    +   'Leaving it untouched.'
                    );

                    return;
                }

                var level       = '    '
                ,   indent      = (( match[ 2 ] != null ) ? match[ 2 ] : ( match[ 1 ] + level ))
                ,   insertAt    = match.index + match[ 0 ].length
                ,   padPre      = match[ 3 ] ? '' : '\n'
                ,   padPost     = match[ 3 ] ? '' : indent
                ,   matcherDef  = /^(([ \t]*)([ \t]+))###\*[\s\S]*?^\1###[\s\S]*?^\1id:\s*'([^\]]*?)'[^\]]*?^\2(?:,[ \t]*(\n)?|(?=(\])))/mg
                //                 ^12======23======31             ^\1           ^\1       4-------4        ^\2          5--5     6--6
                ;

                //  Start looking for definitions directly after API declaration opening.
                //
                matcherDef.lastIndex    = insertAt;

                //  Find a place to insert
                //
                while ( (( match = matcherDef.exec( collection ) )) )
                {
                    level   = match[ 3 ];

                    if ( modelName > match[ 4 ] )
                    {
                        //  Possibly insert after this match.
                        //
                        indent      = match[ 2 ];
                        insertAt    = match.index + match[ 0 ].length;
                        padPre      = match[ 5 ] ? '' : match[ 6 ] ? ',\n'  : '\n';
                        padPost     = match[ 5 ] ? '' : indent;
                        continue;
                    }

                    if ( modelName < match[ 4 ] )
                    {
                        //  Insert before this match.
                        //
                        insertAt    = match.index;
                        padPre      = '';
                        padPost     = '';
                        break;
                    }

                    this.log(
                        'It appears that "' + api.pathAbs + '" already contains a service API endpoint definition for "' + data.className + '".\n'
                    +   'Leaving it untouched.'
                    );

                    return;
                }

                //  Avoid conflict warning.
                //
                this.conflicter.force   = true;

                //  Expand fragment template and read it back.
                //
                var fragmentPath        = 'src/apis/api-service-literal-fragment.coffee'
                ,   fragmentDst         = this.destinationPath( fragmentPath )
                ;

                this._templatesProcess( [ [ fragmentPath ] ] );

                var fragment            = fs.read( fragmentDst );

                fs.write(
                    api.pathAbs
                ,   collection.slice( 0, insertAt )
                +   padPre
                +   fragment.replace( /^    /mg, level ).replace( /^(?=.*?\S)/mg, indent )
                +   padPost
                +   collection.slice( insertAt )
                );

                fs.delete( fragmentDst );
            }
        }
    }
);

_.extend(
    ModelGenerator.prototype
,   require( './../../lib/generator.js' )
,   require( './../../lib/sub-generator.js' )
);

module.exports = ModelGenerator;
<|endoftext|>


<commit_before>/**
 * Visual Blocks Language
 *
 * Copyright 2012 Google Inc.
 * http://code.google.com/p/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Generating Python for text blocks.
 * @author fraser@google.com (Neil Fraser)
 * Due to the frequency of long strings, the 80-column wrap rule need not apply
 * to language files.
 */

Blockly.Python = Blockly.Generator.get('Python');

Blockly.Python.text = function() {
  // Text value.
  return Blockly.Python.quote_(this.getTitleText('TEXT'));
};

Blockly.Python.text_join = function(opt_dropParens) {
  // Create a string made up of any number of elements of any type.
  //Should we allow joining by '-' or ',' or any other characters?
  if (this.itemCount_ == 0) {
    return '\'\'';
  } else if (this.itemCount_ == 1) {
    return 'str(' + (Blockly.Python.valueToCode(this, 'ADD0', opt_dropParens) || '\'\'') + ')';
  } else if (this.itemCount_ == 2) {
    var argument0 = Blockly.Python.valueToCode(this, 'ADD0', true) || '\'\'';
    var argument1 = Blockly.Python.valueToCode(this, 'ADD1', true) || '\'\'';
    var code = 'str(' + argument0 + ') + str(' + argument1 + ')';
    if (!opt_dropParens) {
      code = '(' + code + ')';
    }
    return code;
  } else {
    var code = [];
    for (n = 0; n < this.itemCount_; n++) {
      code[n] = Blockly.Python.valueToCode(this, 'ADD' + n, true) || '\'\'';
    }
    var tempVar = Blockly.Python.variableDB_.getDistinctName('temp_value',
        Blockly.Variables.NAME_TYPE);
    code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' + code.join(', ') + ']])';
    if (!opt_dropParens) {
      code = '(' + code + ')';
    }
    return code;
  }
};

Blockly.Python.text_length = function() {
  // String length.
  var argument0 = Blockly.Python.valueToCode(this, 'VALUE', true) || '\'\'';
  return 'len(' + argument0 + ')';
};

Blockly.Python.text_isEmpty = function(opt_dropParens) {
  // Is the string null?
  var argument0 = Blockly.Python.valueToCode(this, 'VALUE', true) || '\'\'';
  var code = 'not len(' + argument0 + ')';
  if (!opt_dropParens) {
    code = '(' + code + ')';
  }
  return code;
};

Blockly.Python.text_endString = function() {
  // Return a leading or trailing substring.
  // Do we need to prevent 'List index out of range' ERROR by checking
  // if argument 0 > len(argument1)? Or will ALL error be handled systematically?
  var first = this.getInputLabelValue('NUM') == 'FIRST';
  var argument0 = Blockly.Python.valueToCode(this, 'NUM', true) || '1';
  var argument1 = Blockly.Python.valueToCode(this, 'TEXT') || '\'\'';
  var code = argument1 + '[' +
      (first ? ':' + argument0 : '-' + argument0 + ':') + ']';
  return code;
};

Blockly.Python.text_indexOf = function(opt_dropParens) {
  // Search the text for a substring.
  // Should we allow for non-case sensitive???
  var operator = this.getTitleValue('END') == 'FIRST' ? 'find' : 'rfind';
  var argument0 = Blockly.Python.valueToCode(this, 'FIND') || '\'\'';
  var argument1 = Blockly.Python.valueToCode(this, 'VALUE') || '\'\'';
  var code = argument1 + '.' + operator + '(' + argument0 + ') + 1';
  if (!opt_dropParens) {
    code = '(' + code + ')';
  }
  return code;
};

Blockly.Python.text_charAt = function() {
  // Get letter at index.
  var argument0 = Blockly.Python.valueToCode(this, 'AT', true) || '1';
  var argument1 = Blockly.Python.valueToCode(this, 'VALUE') || '[]';
  // Blockly uses one-based indicies.
  if (argument0.match(/^\d+$/)) {
    // If the index is a naked number, decrement it right now.
    // Except not allowing negative index by constraining at 0.
    argument0 = Math.max(0, parseInt(argument0, 10) - 1);
  } else {
    // If the index is dynamic, decrement it in code.
    argument0 += ' - 1';
  }
  return argument1 + '[' + argument0 + ']';
};

Blockly.Python.text_changeCase = function() {
  // Change capitalization.
  var mode = this.getInputLabelValue('TEXT');
  var operator = Blockly.JavaScript.text_changeCase.OPERATORS[mode];
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT', true) || '\'\'';
  var code = argument0 + operator;
  return code;
};

Blockly.Python.text_changeCase.OPERATORS = {
  UPPERCASE: '.upper()',
  LOWERCASE: '.lower()',
  TITLECASE: '.title()'
};

Blockly.Python.text_trim = function() {
  // Trim spaces.
  var mode = this.getTitleValue('MODE');
  var operator = Blockly.JavaScript.text_trim.OPERATORS[mode];
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT') || '\'\'';
  return argument0 + operator;
};

Blockly.Python.text_trim.OPERATORS = {
  LEFT: '.lstrip()',
  RIGHT: '.rstrip()',
  BOTH: '.strip()'
};

Blockly.Python.text_print = function() {
  // Print statement.
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT', true) || '\'\'';
  return 'print ' + argument0 + '\n';
};

Blockly.Python.text_prompt = function() {
  // Prompt function.
  var msg = Blockly.Python.quote_(this.getTitleValue('TEXT'));
  var code = 'raw_input(' + msg + ')';
  var toNumber = this.getTitleValue('TYPE') == 'NUMBER';
  if (toNumber) {
    code = 'float(' + code + ')';
  }
  return code;
};
<commit_msg>Fix Python generation errors in text blocks.<commit_after>/**
 * Visual Blocks Language
 *
 * Copyright 2012 Google Inc.
 * http://code.google.com/p/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Generating Python for text blocks.
 * @author fraser@google.com (Neil Fraser)
 * Due to the frequency of long strings, the 80-column wrap rule need not apply
 * to language files.
 */

Blockly.Python = Blockly.Generator.get('Python');

Blockly.Python.text = function() {
  // Text value.
  return Blockly.Python.quote_(this.getTitleText('TEXT'));
};

Blockly.Python.text_join = function(opt_dropParens) {
  // Create a string made up of any number of elements of any type.
  //Should we allow joining by '-' or ',' or any other characters?
  if (this.itemCount_ == 0) {
    return '\'\'';
  } else if (this.itemCount_ == 1) {
    return 'str(' + (Blockly.Python.valueToCode(this, 'ADD0', opt_dropParens) || '\'\'') + ')';
  } else if (this.itemCount_ == 2) {
    var argument0 = Blockly.Python.valueToCode(this, 'ADD0', true) || '\'\'';
    var argument1 = Blockly.Python.valueToCode(this, 'ADD1', true) || '\'\'';
    var code = 'str(' + argument0 + ') + str(' + argument1 + ')';
    if (!opt_dropParens) {
      code = '(' + code + ')';
    }
    return code;
  } else {
    var code = [];
    for (n = 0; n < this.itemCount_; n++) {
      code[n] = Blockly.Python.valueToCode(this, 'ADD' + n, true) || '\'\'';
    }
    var tempVar = Blockly.Python.variableDB_.getDistinctName('temp_value',
        Blockly.Variables.NAME_TYPE);
    code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' + code.join(', ') + ']])';
    if (!opt_dropParens) {
      code = '(' + code + ')';
    }
    return code;
  }
};

Blockly.Python.text_length = function() {
  // String length.
  var argument0 = Blockly.Python.valueToCode(this, 'VALUE', true) || '\'\'';
  return 'len(' + argument0 + ')';
};

Blockly.Python.text_isEmpty = function(opt_dropParens) {
  // Is the string null?
  var argument0 = Blockly.Python.valueToCode(this, 'VALUE', true) || '\'\'';
  var code = 'not len(' + argument0 + ')';
  if (!opt_dropParens) {
    code = '(' + code + ')';
  }
  return code;
};

Blockly.Python.text_endString = function() {
  // Return a leading or trailing substring.
  // Do we need to prevent 'List index out of range' ERROR by checking
  // if argument 0 > len(argument1)? Or will ALL error be handled systematically?
  var first = this.getInputLabelValue('NUM') == 'FIRST';
  var argument0 = Blockly.Python.valueToCode(this, 'NUM', true) || '1';
  var argument1 = Blockly.Python.valueToCode(this, 'TEXT') || '\'\'';
  var code = argument1 + '[' +
      (first ? ':' + argument0 : '-' + argument0 + ':') + ']';
  return code;
};

Blockly.Python.text_indexOf = function(opt_dropParens) {
  // Search the text for a substring.
  // Should we allow for non-case sensitive???
  var operator = this.getTitleValue('END') == 'FIRST' ? 'find' : 'rfind';
  var argument0 = Blockly.Python.valueToCode(this, 'FIND') || '\'\'';
  var argument1 = Blockly.Python.valueToCode(this, 'VALUE') || '\'\'';
  var code = argument1 + '.' + operator + '(' + argument0 + ') + 1';
  if (!opt_dropParens) {
    code = '(' + code + ')';
  }
  return code;
};

Blockly.Python.text_charAt = function() {
  // Get letter at index.
  var argument0 = Blockly.Python.valueToCode(this, 'AT', true) || '1';
  var argument1 = Blockly.Python.valueToCode(this, 'VALUE') || '[]';
  // Blockly uses one-based indicies.
  if (argument0.match(/^\d+$/)) {
    // If the index is a naked number, decrement it right now.
    // Except not allowing negative index by constraining at 0.
    argument0 = Math.max(0, parseInt(argument0, 10) - 1);
  } else {
    // If the index is dynamic, decrement it in code.
    argument0 += ' - 1';
  }
  return argument1 + '[' + argument0 + ']';
};

Blockly.Python.text_changeCase = function() {
  // Change capitalization.
  var mode = this.getInputLabelValue('TEXT');
  var operator = Blockly.Python.text_changeCase.OPERATORS[mode];
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT', true) || '\'\'';
  var code = argument0 + operator;
  return code;
};

Blockly.Python.text_changeCase.OPERATORS = {
  UPPERCASE: '.upper()',
  LOWERCASE: '.lower()',
  TITLECASE: '.title()'
};

Blockly.Python.text_trim = function() {
  // Trim spaces.
  var mode = this.getTitleValue('MODE');
  var operator = Blockly.Python.text_trim.OPERATORS[mode];
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT') || '\'\'';
  return argument0 + operator;
};

Blockly.Python.text_trim.OPERATORS = {
  LEFT: '.lstrip()',
  RIGHT: '.rstrip()',
  BOTH: '.strip()'
};

Blockly.Python.text_print = function() {
  // Print statement.
  var argument0 = Blockly.Python.valueToCode(this, 'TEXT', true) || '\'\'';
  return 'print ' + argument0 + '\n';
};

Blockly.Python.text_prompt = function() {
  // Prompt function.
  var msg = Blockly.Python.quote_(this.getTitleValue('TEXT'));
  var code = 'raw_input(' + msg + ')';
  var toNumber = this.getTitleValue('TYPE') == 'NUMBER';
  if (toNumber) {
    code = 'float(' + code + ')';
  }
  return code;
};
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: ScrollView.js                                                        \\
// Module: View                                                               \\
// Requires: Core, Foundation, View.js                                        \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var ScrollAnimation = NS.Class({

    Extends: NS.Animation,

    duration: 250,

    prepare: function ( coordinates ) {
        var object = this.object,
            startX = this.startX = object.get( 'scrollLeft' ),
            startY = this.startY = object.get( 'scrollTop' ),
            endX = this.endX = coordinates.x || 0,
            endY = this.endY = coordinates.y || 0,
            deltaX = this.deltaX = endX - startX,
            deltaY = this.deltaY = endY - startY;

        return !!( deltaX || deltaY );
    },

    drawFrame: function ( position ) {
        var x = position < 1 ?
                this.startX + ( position * this.deltaX ) : this.endY,
            y = position < 1 ?
                this.startY + ( position * this.deltaY ) : this.endY;
        this.object.scrollTo( x, y );
    }
});

var ScrollView = NS.Class({

    Extends: NS.View,

    className: 'ScrollView',

    clipToBounds: true,
    showScrollbarY: true,

    positioning: 'absolute',
    layout: NS.View.LAYOUT_FILL_PARENT,

    keys: {},

    didAppendLayerToDocument: function () {
        // Scroll is reset to 0 some browsers whenever it is removed from the
        // DOM, so we need to set it to what it should be.
        var layer = this.get( 'layer' ),
            left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );

        layer.scrollLeft = left;
        layer.scrollTop = top;
        layer.addEventListener( 'scroll', this, false );

        // Add keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.RootViewController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.register( key, this, keys[ key ] );
        }

        return ScrollView.parent.didAppendLayerToDocument.call( this );
    },

    willRemoveLayerFromDocument: function () {
        // Remove keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.RootViewController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.deregister( key, this, keys[ key ] );
        }

        this.get( 'layer' ).removeEventListener( 'scroll', this, false );

        return ScrollView.parent.willRemoveLayerFromDocument.call( this );
    },

    scrollAnimation: function ( ) {
        return new ScrollAnimation({
            object: this
        });
    }.property(),

    scrollPage: function () {
        return this.scrollBy( 0, this.get( 'pxHeight' ) - 50, true );
    },
    reverseScrollPage: function () {
        return this.scrollBy( 0, 50 - this.get( 'pxHeight' ), true );
    },
    scrollLine: function () {
        return this.scrollBy( 0, 40 );
    },
    reverseScrollLine: function () {
        return this.scrollBy( 0, -40 );
    },

    scrollBy: function ( x, y, withAnimation ) {
        var left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );
        x += left;
        y += top;

        this.scrollTo( x, y, withAnimation );

        return top !== this.get( 'scrollTop' ) ||
            left !== this.get( 'scrollLeft' );
    },

    scrollTo: function ( x, y, withAnimation ) {
        if ( withAnimation ) {
            this.get( 'scrollAnimation' ).animate({
                x: x,
                y: y
            });
            return this;
        }

        // Can't have negative scroll values.
        if ( x < 0 ) { x = 0; }
        if ( y < 0 ) { y = 0; }

        // Can only scroll when in the document; we'll scroll
        // on attachment anyway so just store the values otherwise.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'layer' );
            layer.scrollLeft = x;
            layer.scrollTop = y;
            // We don't know if they were greater than the max allowed value
            // (except if === 0).
            if ( x ) { x = layer.scrollLeft; }
            if ( y ) { y = layer.scrollTop; }
        }

        return this.beginPropertyChanges()
              .set( 'scrollLeft', x )
              .set( 'scrollTop', y )
            .endPropertyChanges();
    },

    scrollToView: function ( view, offset, withAnimation ) {
        var position = NS.Element.getPosition(
            view.get( 'layer' ), this.get( 'layer' ) );
        if ( !offset ) {
            offset = { x: 0, y: 0 };
        }
        return this.scrollTo(
            position.left + offset.x, position.top + offset.y, withAnimation );
    }
});

NS.ScrollView = ScrollView;

}( this.O ) );
<commit_msg>Stop scroll animation if new scroll position set.<commit_after>// -------------------------------------------------------------------------- \\
// File: ScrollView.js                                                        \\
// Module: View                                                               \\
// Requires: Core, Foundation, View.js                                        \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var ScrollAnimation = NS.Class({

    Extends: NS.Animation,

    duration: 250,

    prepare: function ( coordinates ) {
        var object = this.object,
            startX = this.startX = object.get( 'scrollLeft' ),
            startY = this.startY = object.get( 'scrollTop' ),
            endX = this.endX = coordinates.x || 0,
            endY = this.endY = coordinates.y || 0,
            deltaX = this.deltaX = endX - startX,
            deltaY = this.deltaY = endY - startY;

        return !!( deltaX || deltaY );
    },

    drawFrame: function ( position ) {
        var x = position < 1 ?
                this.startX + ( position * this.deltaX ) : this.endY,
            y = position < 1 ?
                this.startY + ( position * this.deltaY ) : this.endY;
        this.object.scrollTo( x, y );
    }
});

var ScrollView = NS.Class({

    Extends: NS.View,

    className: 'ScrollView',

    clipToBounds: true,
    showScrollbarY: true,

    positioning: 'absolute',
    layout: NS.View.LAYOUT_FILL_PARENT,

    keys: {},

    didAppendLayerToDocument: function () {
        // Scroll is reset to 0 some browsers whenever it is removed from the
        // DOM, so we need to set it to what it should be.
        var layer = this.get( 'layer' ),
            left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );

        layer.scrollLeft = left;
        layer.scrollTop = top;
        layer.addEventListener( 'scroll', this, false );

        // Add keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.RootViewController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.register( key, this, keys[ key ] );
        }

        return ScrollView.parent.didAppendLayerToDocument.call( this );
    },

    willRemoveLayerFromDocument: function () {
        // Remove keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.RootViewController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.deregister( key, this, keys[ key ] );
        }

        this.get( 'layer' ).removeEventListener( 'scroll', this, false );

        return ScrollView.parent.willRemoveLayerFromDocument.call( this );
    },

    scrollAnimation: function ( ) {
        return new ScrollAnimation({
            object: this
        });
    }.property(),

    scrollPage: function () {
        return this.scrollBy( 0, this.get( 'pxHeight' ) - 50, true );
    },
    reverseScrollPage: function () {
        return this.scrollBy( 0, 50 - this.get( 'pxHeight' ), true );
    },
    scrollLine: function () {
        return this.scrollBy( 0, 40 );
    },
    reverseScrollLine: function () {
        return this.scrollBy( 0, -40 );
    },

    scrollBy: function ( x, y, withAnimation ) {
        var left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );
        x += left;
        y += top;

        this.scrollTo( x, y, withAnimation );

        return top !== this.get( 'scrollTop' ) ||
            left !== this.get( 'scrollLeft' );
    },

    scrollTo: function ( x, y, withAnimation ) {
        var scrollAnimation = this.get( 'scrollAnimation' );
        if ( withAnimation ) {
            scrollAnimation.animate({
                x: x,
                y: y
            });
            return this;
        } else {
            scrollAnimation.stop();
        }

        // Can't have negative scroll values.
        if ( x < 0 ) { x = 0; }
        if ( y < 0 ) { y = 0; }

        // Can only scroll when in the document; we'll scroll
        // on attachment anyway so just store the values otherwise.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'layer' );
            layer.scrollLeft = x;
            layer.scrollTop = y;
            // We don't know if they were greater than the max allowed value
            // (except if === 0).
            if ( x ) { x = layer.scrollLeft; }
            if ( y ) { y = layer.scrollTop; }
        }

        return this.beginPropertyChanges()
              .set( 'scrollLeft', x )
              .set( 'scrollTop', y )
            .endPropertyChanges();
    },

    scrollToView: function ( view, offset, withAnimation ) {
        var position = NS.Element.getPosition(
            view.get( 'layer' ), this.get( 'layer' ) );
        if ( !offset ) {
            offset = { x: 0, y: 0 };
        }
        return this.scrollTo(
            position.left + offset.x, position.top + offset.y, withAnimation );
    }
});

NS.ScrollView = ScrollView;

}( this.O ) );
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: ScrollView.js                                                        \\
// Module: ContainerViews                                                     \\
// Requires: Core, Foundation, View                                           \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var ScrollAnimation = NS.Class({

    Extends: NS.Animation,

    duration: 250,

    prepare: function ( coordinates ) {
        var object = this.object,
            startX = this.startX = object.get( 'scrollLeft' ),
            startY = this.startY = object.get( 'scrollTop' ),
            endX = this.endX = coordinates.x || 0,
            endY = this.endY = coordinates.y || 0,
            deltaX = this.deltaX = endX - startX,
            deltaY = this.deltaY = endY - startY;

        return !!( deltaX || deltaY );
    },

    drawFrame: function ( position ) {
        var x = position < 1 ?
                this.startX + ( position * this.deltaX ) : this.endY,
            y = position < 1 ?
                this.startY + ( position * this.deltaY ) : this.endY;
        this.object.scrollTo( x, y, false, true );
    }
});

/**
    Class: O.ScrollView

    Extends: O.View

    An O.ScrollView instance is a fixed size container, which can be scrolled if
    its contents overflows the bounds of the view. By default, a scrollbar will
    only be shown for vertical overflow. Set the <O.ScrollView#showScrollbarX>
    property to `true` to show a scrollbar on horizontal overflow as well.
*/
var ScrollView = NS.Class({

    Extends: NS.View,

    /**
        Property: O.ScrollView#className
        Type: String
        Default: 'ScrollView'

        Overrides default in <O.View#className>.
    */
    className: 'ScrollView',

    /**
        Property: O.ScrollView#showScrollbarX
        Type: Boolean
        Default: false

        Show a scrollbar if the content horizontally overflows the bounds of the
        DOM element representing this view?
    */
    showScrollbarX: false,

    /**
        Property: O.ScrollView#showScrollbarY
        Type: Boolean
        Default: true

        Show a scrollbar if the content vertically overflows the bounds of the
        DOM element representing this view?
    */
    showScrollbarY: true,

    /**
        Property: O.ScrollView#positioning
        Type: String
        Default: 'absolute'

        Overrides default in <O.View#positioning>.
   */
    positioning: 'absolute',

    /**
        Property: O.ScrollView#layout
        Type: Object
        Default:
                {
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: '100%'
                }

        Overrides default in <O.View#layout>.
    */
    layout: NS.View.LAYOUT_FILL_PARENT,

    /**
        Property: O.ScrollView#layerStyles
        Type: Object

        Sets the overflow styles to show the scrollbars.
    */
    layerStyles: function () {
        var styles = NS.View.prototype.layerStyles.call( this );
        styles.overflowX = this.get( 'showScrollbarX' ) ? 'auto' : 'hidden';
        styles.overflowY = this.get( 'showScrollbarY' ) ? 'auto' : 'hidden';
        return styles;
    }.property( 'layout', 'allowTextSelection', 'positioning',
        'showScrollbarX', 'showScrollbarY' ),

    /**
        Property: O.ScrollView#keys
        Type: Object
        Default: {}

        Keyboard shortcuts to scroll the view. A map of keyboard shortcut to the
        method name to call on the O.ScrollView instance. These shortcuts will
        automatically be activated/deactivated when the view is added/removed
        to/from the document.

        For example, on the main scroll view for you content, you might set:

            {
                'pagedown': 'scrollPage',
                'pageup': 'reverseScrollPage',
                'space': 'scrollPage',
                'shift-space': 'reverseScrollPage',
                'down': 'scrollLine',
                'up': 'reverseScrollLine'
            }
    */
    keys: {},

    didCreateLayer: function ( layer ) {
        this.scrollLayer = layer;
    },

    didEnterDocument: function () {
        this.get( 'scrollLayer' ).addEventListener( 'scroll', this, false );

        // Add keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.ViewEventsController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.register( key, this, keys[ key ] );
        }

        return ScrollView.parent.didEnterDocument.call( this );
    },

    willLeaveDocument: function () {
        // Remove keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.ViewEventsController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.deregister( key, this, keys[ key ] );
        }

        this.get( 'scrollLayer' ).removeEventListener( 'scroll', this, false );

        return ScrollView.parent.willLeaveDocument.call( this );
    },

    _restoreScroll: function () {
        // Scroll is reset to 0 in some browsers whenever it is removed from the
        // DOM, so we need to set it to what it should be.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'scrollLayer' );
            layer.scrollLeft = this.get( 'scrollLeft' );
            layer.scrollTop = this.get( 'scrollTop' );
        }
    }.queue( 'after' ).observes( 'isInDocument' ),

    /**
        Property: O.ScrollView#scrollAnimation
        Type: O.Animation

        An <O.Animation> object to animate scrolling on this object. Normally
        you will not need to interact with this directly, but just set the
        `withAnimation` argument to `true` when you call O.ScrollView#scrollTo.
        However, if you wish to change the duration or easing method, you can do
        so by setting it on this object.
    */
    scrollAnimation: function ( ) {
        return new ScrollAnimation({
            object: this
        });
    }.property(),

    /**
        Method: O.ScrollView#scrollPage

        Scrolls the view down by the view height - 50px.
    */
    scrollPage: function () {
        return this.scrollBy( 0, this.get( 'pxHeight' ) - 50, true );
    },

    /**
        Method: O.ScrollView#reverseScrollPage

        Scrolls the view up by the view height - 50px.
    */
    reverseScrollPage: function () {
        return this.scrollBy( 0, 50 - this.get( 'pxHeight' ), true );
    },

    /**
        Method: O.ScrollView#scrollLine

        Scrolls the view down by 40px.
    */
    scrollLine: function () {
        return this.scrollBy( 0, 40 );
    },

    /**
        Method: O.ScrollView#reverseScrollLine

        Scrolls the view up by 40px.
    */
    reverseScrollLine: function () {
        return this.scrollBy( 0, -40 );
    },

    /**
        Method: O.ScrollView#scrollBy

        Scroll the view by the given number of pixels (use negative values to
        scroll up/left).

        Parameters:
            x             - {Number} The number of pixels to scroll right.
            y             - {Number} The number of pixels to scroll down.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {Boolean} Did the view actually scroll (false if already at end)?
    */
    scrollBy: function ( x, y, withAnimation ) {
        var left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );
        x += left;
        y += top;

        this.scrollTo( x, y, withAnimation );

        return top !== this.get( 'scrollTop' ) ||
            left !== this.get( 'scrollLeft' );
    },

    /**
        Method: O.ScrollView#scrollBy

        Scroll the view to a given position, where (0,0) represents the scroll
        view fully .

        Parameters:
            x             - {Number} The number of pixels to set the horizontal
                            scroll-position to.
            y             - {Number} The number of pixels to set the vertical
                            scroll-position to.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {O.ScrollView} Returns self.
    */
    scrollTo: function ( x, y, withAnimation, _isAnimation ) {
        var scrollAnimation = this.get( 'scrollAnimation' );
        if ( withAnimation ) {
            scrollAnimation.animate({
                x: x,
                y: y
            });
            return this;
        } else if ( !_isAnimation ) {
            scrollAnimation.stop();
        }

        // Can't have negative scroll values.
        if ( x < 0 ) { x = 0; }
        if ( y < 0 ) { y = 0; }

        // Can only scroll when in the document; we'll scroll
        // on attachment anyway so just store the values otherwise.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'scrollLayer' );
            layer.scrollLeft = x;
            layer.scrollTop = y;
            // We don't know if they were greater than the max allowed value
            // (except if === 0).
            if ( x ) { x = layer.scrollLeft; }
            if ( y ) { y = layer.scrollTop; }
        }

        return this.beginPropertyChanges()
              .set( 'scrollLeft', x )
              .set( 'scrollTop', y )
            .endPropertyChanges();
    },

    /**
        Method: O.ScrollView#scrollToView

        Scroll the view to show a sub-view in the top left of the view.

        Parameters:
            view          - {View} The sub-view to scroll to.
            offset        - {Object} (optional) If supplied, must contain
                            numerical `x` and `y` properties which give the
                            number of pixels to offset the subview from the top
                            left of the scroll view.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {O.ScrollView} Returns self.
    */
    scrollToView: function ( view, offset, withAnimation ) {
        var position = NS.Element.getPosition(
            view.get( 'layer' ), this.get( 'layer' ) );
        if ( !offset ) {
            offset = { x: 0, y: 0 };
        }
        return this.scrollTo(
            position.left + offset.x, position.top + offset.y, withAnimation );
    },

    /**
        Method (private): O.ScrollView#_onScroll

        Parameters:
            event - {Event} The scroll event object.

        Updates the view properties when the layer scrolls.
    */
    _onScroll: function ( event ) {
        var layer = this.get( 'scrollLayer' ),
            left = layer.scrollLeft,
            top = layer.scrollTop;
        this.beginPropertyChanges()
            .set( 'scrollLeft', left )
            .set( 'scrollTop', top )
            .endPropertyChanges();
        event.stopPropagation();
        // Don't interpret tap to stop scroll as a real tap.
        if ( NS.Tap ) {
            NS.Tap.cancel();
        }
    }.on( 'scroll' )
});

if ( NS.UA.isIOS ) {
    ScrollView.implement({
        draw: function ( layer ) {
            var el = NS.Element.create,
                contents, scrollFixerHeight, wrapper;

            // Preferred solution if height of div is fixed.
            if ( this.get( 'positioning' ) === 'absolute' ) {
                wrapper = this.scrollLayer = el( 'div', {
                    style: 'position:relative;height:100%;overflow:auto;' +
                        '-webkit-overflow-scrolling:touch;'
                });
                layer.appendChild( wrapper );
                scrollFixerHeight = '1';
            }
            // Fallback solution if we can't be sure.
            else {
                wrapper = layer;
                this.on( 'scroll', this, '_iosScroll' )
                    .addObserverForKey( 'isInDocument', this, '_iosScroll' );
                scrollFixerHeight = '2';
            }
            wrapper.appendChild(
                contents = el( 'div.ScrollViewContents', {
                    style: 'position:relative;' + (
                        this.get( 'showScrollbarX' ) ? '' : 'overflow:hidden;'
                    )
                })
            );
            wrapper.appendChild(
                el( 'div', {
                    style: 'position:absolute;top:100%;left: 0px;' +
                        'width:1px;height:' + scrollFixerHeight + 'px;'
                })
            );
            ScrollView.parent.draw.call( this, contents );
        },
        _iosScroll: function () {
            if ( this.get( 'isInDocument' ) ) {
                var scrollTop = this.get( 'scrollTop' ),
                    scrollLeft = this.get( 'scrollLeft' );
                if ( !scrollTop ) {
                    this.scrollTo( scrollLeft, 1 );
                } else if ( scrollTop + this.get( 'pxHeight' ) ===
                        this.get( 'layer' ).scrollHeight ) {
                    this.scrollTo( scrollLeft, scrollTop - 1 );
                }
            }
        }.queue( 'after' ),
        insertView: function ( view, relativeTo, where ) {
            if ( !relativeTo && this.get( 'isRendered' ) &&
                    where !== 'before' && where !== 'after' ) {
                relativeTo = this.get( 'scrollLayer' ).firstChild;
            }
            return ScrollView.parent.insertView.call(
                this, view, relativeTo, where );
        }
    });
}

NS.ScrollView = ScrollView;

}( this.O ) );
<commit_msg>Fix bug animating ScrollView.<commit_after>// -------------------------------------------------------------------------- \\
// File: ScrollView.js                                                        \\
// Module: ContainerViews                                                     \\
// Requires: Core, Foundation, View                                           \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var ScrollAnimation = NS.Class({

    Extends: NS.Animation,

    duration: 250,

    prepare: function ( coordinates ) {
        var object = this.object,
            startX = this.startX = object.get( 'scrollLeft' ),
            startY = this.startY = object.get( 'scrollTop' ),
            endX = this.endX = coordinates.x || 0,
            endY = this.endY = coordinates.y || 0,
            deltaX = this.deltaX = endX - startX,
            deltaY = this.deltaY = endY - startY;

        return !!( deltaX || deltaY );
    },

    drawFrame: function ( position ) {
        var x = position < 1 ?
                this.startX + ( position * this.deltaX ) : this.endX,
            y = position < 1 ?
                this.startY + ( position * this.deltaY ) : this.endY;
        this.object.scrollTo( x, y, false, true );
    }
});

/**
    Class: O.ScrollView

    Extends: O.View

    An O.ScrollView instance is a fixed size container, which can be scrolled if
    its contents overflows the bounds of the view. By default, a scrollbar will
    only be shown for vertical overflow. Set the <O.ScrollView#showScrollbarX>
    property to `true` to show a scrollbar on horizontal overflow as well.
*/
var ScrollView = NS.Class({

    Extends: NS.View,

    /**
        Property: O.ScrollView#className
        Type: String
        Default: 'ScrollView'

        Overrides default in <O.View#className>.
    */
    className: 'ScrollView',

    /**
        Property: O.ScrollView#showScrollbarX
        Type: Boolean
        Default: false

        Show a scrollbar if the content horizontally overflows the bounds of the
        DOM element representing this view?
    */
    showScrollbarX: false,

    /**
        Property: O.ScrollView#showScrollbarY
        Type: Boolean
        Default: true

        Show a scrollbar if the content vertically overflows the bounds of the
        DOM element representing this view?
    */
    showScrollbarY: true,

    /**
        Property: O.ScrollView#positioning
        Type: String
        Default: 'absolute'

        Overrides default in <O.View#positioning>.
   */
    positioning: 'absolute',

    /**
        Property: O.ScrollView#layout
        Type: Object
        Default:
                {
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: '100%'
                }

        Overrides default in <O.View#layout>.
    */
    layout: NS.View.LAYOUT_FILL_PARENT,

    /**
        Property: O.ScrollView#layerStyles
        Type: Object

        Sets the overflow styles to show the scrollbars.
    */
    layerStyles: function () {
        var styles = NS.View.prototype.layerStyles.call( this );
        styles.overflowX = this.get( 'showScrollbarX' ) ? 'auto' : 'hidden';
        styles.overflowY = this.get( 'showScrollbarY' ) ? 'auto' : 'hidden';
        return styles;
    }.property( 'layout', 'allowTextSelection', 'positioning',
        'showScrollbarX', 'showScrollbarY' ),

    /**
        Property: O.ScrollView#keys
        Type: Object
        Default: {}

        Keyboard shortcuts to scroll the view. A map of keyboard shortcut to the
        method name to call on the O.ScrollView instance. These shortcuts will
        automatically be activated/deactivated when the view is added/removed
        to/from the document.

        For example, on the main scroll view for you content, you might set:

            {
                'pagedown': 'scrollPage',
                'pageup': 'reverseScrollPage',
                'space': 'scrollPage',
                'shift-space': 'reverseScrollPage',
                'down': 'scrollLine',
                'up': 'reverseScrollLine'
            }
    */
    keys: {},

    didCreateLayer: function ( layer ) {
        this.scrollLayer = layer;
    },

    didEnterDocument: function () {
        this.get( 'scrollLayer' ).addEventListener( 'scroll', this, false );

        // Add keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.ViewEventsController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.register( key, this, keys[ key ] );
        }

        return ScrollView.parent.didEnterDocument.call( this );
    },

    willLeaveDocument: function () {
        // Remove keyboard shortcuts:
        var keys = this.get( 'keys' ),
            shortcuts = NS.ViewEventsController.kbShortcuts,
            key;
        for ( key in keys ) {
            shortcuts.deregister( key, this, keys[ key ] );
        }

        this.get( 'scrollLayer' ).removeEventListener( 'scroll', this, false );

        return ScrollView.parent.willLeaveDocument.call( this );
    },

    _restoreScroll: function () {
        // Scroll is reset to 0 in some browsers whenever it is removed from the
        // DOM, so we need to set it to what it should be.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'scrollLayer' );
            layer.scrollLeft = this.get( 'scrollLeft' );
            layer.scrollTop = this.get( 'scrollTop' );
        }
    }.queue( 'after' ).observes( 'isInDocument' ),

    /**
        Property: O.ScrollView#scrollAnimation
        Type: O.Animation

        An <O.Animation> object to animate scrolling on this object. Normally
        you will not need to interact with this directly, but just set the
        `withAnimation` argument to `true` when you call O.ScrollView#scrollTo.
        However, if you wish to change the duration or easing method, you can do
        so by setting it on this object.
    */
    scrollAnimation: function ( ) {
        return new ScrollAnimation({
            object: this
        });
    }.property(),

    /**
        Method: O.ScrollView#scrollPage

        Scrolls the view down by the view height - 50px.
    */
    scrollPage: function () {
        return this.scrollBy( 0, this.get( 'pxHeight' ) - 50, true );
    },

    /**
        Method: O.ScrollView#reverseScrollPage

        Scrolls the view up by the view height - 50px.
    */
    reverseScrollPage: function () {
        return this.scrollBy( 0, 50 - this.get( 'pxHeight' ), true );
    },

    /**
        Method: O.ScrollView#scrollLine

        Scrolls the view down by 40px.
    */
    scrollLine: function () {
        return this.scrollBy( 0, 40 );
    },

    /**
        Method: O.ScrollView#reverseScrollLine

        Scrolls the view up by 40px.
    */
    reverseScrollLine: function () {
        return this.scrollBy( 0, -40 );
    },

    /**
        Method: O.ScrollView#scrollBy

        Scroll the view by the given number of pixels (use negative values to
        scroll up/left).

        Parameters:
            x             - {Number} The number of pixels to scroll right.
            y             - {Number} The number of pixels to scroll down.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {Boolean} Did the view actually scroll (false if already at end)?
    */
    scrollBy: function ( x, y, withAnimation ) {
        var left = this.get( 'scrollLeft' ),
            top = this.get( 'scrollTop' );
        x += left;
        y += top;

        this.scrollTo( x, y, withAnimation );

        return top !== this.get( 'scrollTop' ) ||
            left !== this.get( 'scrollLeft' );
    },

    /**
        Method: O.ScrollView#scrollBy

        Scroll the view to a given position, where (0,0) represents the scroll
        view fully .

        Parameters:
            x             - {Number} The number of pixels to set the horizontal
                            scroll-position to.
            y             - {Number} The number of pixels to set the vertical
                            scroll-position to.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {O.ScrollView} Returns self.
    */
    scrollTo: function ( x, y, withAnimation, _isAnimation ) {
        var scrollAnimation = this.get( 'scrollAnimation' );
        if ( withAnimation ) {
            scrollAnimation.animate({
                x: x,
                y: y
            });
            return this;
        } else if ( !_isAnimation ) {
            scrollAnimation.stop();
        }

        // Can't have negative scroll values.
        if ( x < 0 ) { x = 0; }
        if ( y < 0 ) { y = 0; }

        // Can only scroll when in the document; we'll scroll
        // on attachment anyway so just store the values otherwise.
        if ( this.get( 'isInDocument' ) ) {
            var layer = this.get( 'scrollLayer' );
            layer.scrollLeft = x;
            layer.scrollTop = y;
            // We don't know if they were greater than the max allowed value
            // (except if === 0).
            if ( x ) { x = layer.scrollLeft; }
            if ( y ) { y = layer.scrollTop; }
        }

        return this.beginPropertyChanges()
              .set( 'scrollLeft', x )
              .set( 'scrollTop', y )
            .endPropertyChanges();
    },

    /**
        Method: O.ScrollView#scrollToView

        Scroll the view to show a sub-view in the top left of the view.

        Parameters:
            view          - {View} The sub-view to scroll to.
            offset        - {Object} (optional) If supplied, must contain
                            numerical `x` and `y` properties which give the
                            number of pixels to offset the subview from the top
                            left of the scroll view.
            withAnimation - {Boolean} (optional) If true, animate the scroll.

        Returns:
            {O.ScrollView} Returns self.
    */
    scrollToView: function ( view, offset, withAnimation ) {
        var position = NS.Element.getPosition(
            view.get( 'layer' ), this.get( 'layer' ) );
        if ( !offset ) {
            offset = { x: 0, y: 0 };
        }
        return this.scrollTo(
            position.left + offset.x, position.top + offset.y, withAnimation );
    },

    /**
        Method (private): O.ScrollView#_onScroll

        Parameters:
            event - {Event} The scroll event object.

        Updates the view properties when the layer scrolls.
    */
    _onScroll: function ( event ) {
        var layer = this.get( 'scrollLayer' ),
            left = layer.scrollLeft,
            top = layer.scrollTop;
        this.beginPropertyChanges()
            .set( 'scrollLeft', left )
            .set( 'scrollTop', top )
            .endPropertyChanges();
        event.stopPropagation();
        // Don't interpret tap to stop scroll as a real tap.
        if ( NS.Tap ) {
            NS.Tap.cancel();
        }
    }.on( 'scroll' )
});

if ( NS.UA.isIOS ) {
    ScrollView.implement({
        draw: function ( layer ) {
            var el = NS.Element.create,
                contents, scrollFixerHeight, wrapper;

            // Preferred solution if height of div is fixed.
            if ( this.get( 'positioning' ) === 'absolute' ) {
                wrapper = this.scrollLayer = el( 'div', {
                    style: 'position:relative;height:100%;overflow:auto;' +
                        '-webkit-overflow-scrolling:touch;'
                });
                layer.appendChild( wrapper );
                scrollFixerHeight = '1';
            }
            // Fallback solution if we can't be sure.
            else {
                wrapper = layer;
                this.on( 'scroll', this, '_iosScroll' )
                    .addObserverForKey( 'isInDocument', this, '_iosScroll' );
                scrollFixerHeight = '2';
            }
            wrapper.appendChild(
                contents = el( 'div.ScrollViewContents', {
                    style: 'position:relative;' + (
                        this.get( 'showScrollbarX' ) ? '' : 'overflow:hidden;'
                    )
                })
            );
            wrapper.appendChild(
                el( 'div', {
                    style: 'position:absolute;top:100%;left: 0px;' +
                        'width:1px;height:' + scrollFixerHeight + 'px;'
                })
            );
            ScrollView.parent.draw.call( this, contents );
        },
        _iosScroll: function () {
            if ( this.get( 'isInDocument' ) ) {
                var scrollTop = this.get( 'scrollTop' ),
                    scrollLeft = this.get( 'scrollLeft' );
                if ( !scrollTop ) {
                    this.scrollTo( scrollLeft, 1 );
                } else if ( scrollTop + this.get( 'pxHeight' ) ===
                        this.get( 'layer' ).scrollHeight ) {
                    this.scrollTo( scrollLeft, scrollTop - 1 );
                }
            }
        }.queue( 'after' ),
        insertView: function ( view, relativeTo, where ) {
            if ( !relativeTo && this.get( 'isRendered' ) &&
                    where !== 'before' && where !== 'after' ) {
                relativeTo = this.get( 'scrollLayer' ).firstChild;
            }
            return ScrollView.parent.insertView.call(
                this, view, relativeTo, where );
        }
    });
}

NS.ScrollView = ScrollView;

}( this.O ) );
<|endoftext|>


<commit_before>function Game(canvasID, width, height, functions, fps) {
    Group.call(this);
    var self = this;

    var canvas = document.getElementById(canvasID);

    this.ctx = canvas.getContext("2d");
    this.ctx.canvas.width = width || 500;
    this.ctx.canvas.height = height || 500;

    canvas.addEventListener("mousedown", this.set(self, this._click), false);

    this.xOffset = canvas.offsetLeft;
    this.yOffset = canvas.offsetTop;

    fps = fps || 30;

    document.addEventListener("DOMContentLoaded", function(event) {
        functions.create();

        setInterval(function() {
            self.update();
            self.draw(self.ctx);
        });
    }, 1000/fps);
}

Game.prototype = Object.create(Group.prototype);
Game.prototype.contstructor = Game;

Game.prototype.set = function(self, func) {
    return function(event) {
        func.call(self, event);
    };
}

Game.prototype._click = function(event) {
    for (var i = 0; i < this._contents.length; i++) {
        var entity = this._contents[i];

        if (typeof entity.onClick == 'function') {
            entity.onClick({x: event.x - this.xOffset, y: event.y - this.yOffset});
        }
    }
}
/*
Game.prototype.update = function() {
    _entities.forEach(function(entity) {
        entity.update();
    });
}

Game.prototype.draw = function() {
    var self = this;

    this.ctx.clearRect(0, 0, 500, 500);
    _entities.forEach(function(entity) {
        entity.draw(self.ctx);
    });
}

Game.prototype.add = function(object) {
    console.log(object);
    _entities.push(object);
}*/

function Entity(graphic, position, size) {
    this.position = position || {x: 100, y: 100};
    this.size = size || {width: 50, height: 50};
    this.graphic = graphic || new RectangleGraphic();

    this.graphic.position = this.position;
    this.graphic.size = this.size;
}

Entity.prototype.draw = function(ctx) {
    this.graphic.draw(ctx);
}

Entity.prototype.overlapPoint = function(point) {
    if (point.x > this.position.x && point.x < this.position.x + this.size.width &&
        point.y > this.position.y && point.y < this.position.y + this.size.height) {
        return true;
    }

    return false;
}

Entity.prototype.update = function() {}

function RectangleGraphic(color) {
    this.color = color || "#DDD";
    this.position;
    this.size;
}

RectangleGraphic.prototype.draw = function(ctx) {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.position.x, this.position.y, this.size.width, this.size.height);
}

function TextGraphic(text, color) {
    console.log("text graphic");
    this.text = text || "";
    this.color = color || 'rgb(20, 200, 30)';
    this.position;
    this.size;
}

TextGraphic.prototype.draw = function(ctx) {
    ctx.fillStyle = this.color;
    ctx.fillText(this.text, this.position.x, this.position.y);
    console.log(this.position);
}

function Group() {
    this._contents = [];
}

Group.prototype.add = function(entity) {
    this._contents.push(entity);
}

Group.prototype.draw = function(ctx) {
    this._contents.forEach(function(entity) {
        entity.draw(ctx);
    });
}

Group.prototype.update = function(ctx) {
    this._contents.forEach(function(entity) {
        entity.update();
    });
}

Group.prototype.onClick = function(point) {
    this._contents.forEach(function(entity) {
        if (typeof entity.onClick == 'function') {
            entity.onClick(point);
        }
    });
}

<commit_msg>Fix formatting in engine<commit_after>function Game(canvasID, width, height, functions, fps) {
    Group.call(this);
    var self = this;

    var canvas = document.getElementById(canvasID);

    this.ctx = canvas.getContext("2d");
    this.ctx.canvas.width = width || 500;
    this.ctx.canvas.height = height || 500;

    canvas.addEventListener("mousedown", this.set(self, this._click), false);

    this.xOffset = canvas.offsetLeft;
    this.yOffset = canvas.offsetTop;

    fps = fps || 30;

    document.addEventListener("DOMContentLoaded", function(event) {
        functions.create();

        setInterval(function() {
            self.update();
            self.draw(self.ctx);
        });
    }, 1000/fps);
}

Game.prototype = Object.create(Group.prototype);
Game.prototype.contstructor = Game;

Game.prototype.set = function(self, func) {
    return function(event) {
        func.call(self, event);
    };
}

Game.prototype._click = function(event) {
    for (var i = 0; i < this._contents.length; i++) {
        var entity = this._contents[i];

        if (typeof entity.onClick == 'function') {
            entity.onClick({x: event.x - this.xOffset, y: event.y - this.yOffset});
        }
    }
}

function Entity(graphic, position, size) {
    this.position = position || {x: 100, y: 100};
    this.size = size || {width: 50, height: 50};
    this.graphic = graphic || new RectangleGraphic();

    this.graphic.position = this.position;
    this.graphic.size = this.size;
}

Entity.prototype.draw = function(ctx) {
    this.graphic.draw(ctx);
}

Entity.prototype.overlapPoint = function(point) {
    if (point.x > this.position.x && point.x < this.position.x + this.size.width &&
        point.y > this.position.y && point.y < this.position.y + this.size.height) {
        return true;
    }

    return false;
}

Entity.prototype.update = function() {}

function RectangleGraphic(color) {
    this.color = color || "#DDD";
    this.position;
    this.size;
}

RectangleGraphic.prototype.draw = function(ctx) {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.position.x, this.position.y, this.size.width, this.size.height);
}

function TextGraphic(text, color) {
    console.log("text graphic");
    this.text = text || "";
    this.color = color || 'rgb(20, 200, 30)';
    this.position;
    this.size;
}

TextGraphic.prototype.draw = function(ctx) {
    ctx.fillStyle = this.color;
    ctx.fillText(this.text, this.position.x, this.position.y);
    //console.log(this.position);
}

function Group() {
    this._contents = [];
}

Group.prototype.add = function(entity) {
    this._contents.push(entity);
}

Group.prototype.draw = function(ctx) {
    this._contents.forEach(function(entity) {
        entity.draw(ctx);
    });
}

Group.prototype.update = function(ctx) {
    this._contents.forEach(function(entity) {
        entity.update();
    });
}

Group.prototype.onClick = function(point) {
    this._contents.forEach(function(entity) {
        if (typeof entity.onClick == 'function') {
            entity.onClick(point);
        }
    });
}

<|endoftext|>


<commit_before>import React from "react";

import {
  CodePane,
  Fill,
  Layout
} from "spectacle";

const CodeSplit = function ({children, source}) {
  return (
    <Layout>
      <Fill style={{paddingRight: "5px"}}>
        <CodePane
          lang="jsx"
          source={source}
        />
      </Fill>
      <Fill style={{paddingLeft: "5px"}}>
        {children}
      </Fill>
    </Layout>
  );
};

export default CodeSplit;
<commit_msg>Codesplit now has lang prop.<commit_after>import React from "react";

import {
  CodePane,
  Fill,
  Layout
} from "spectacle";

const CodeSplit = function ({children, source, lang = "jsx"}) {
  return (
    <Layout>
      <Fill style={{paddingRight: "5px"}}>
        <CodePane
          lang={lang}
          source={source}
        />
      </Fill>
      <Fill style={{paddingLeft: "5px"}}>
        {children}
      </Fill>
    </Layout>
  );
};

export default CodeSplit;
<|endoftext|>


<commit_before>'use strict';

var consolidate = require('consolidate');
var path        = require('path');
var _           = require('lodash');

module.exports = {

    partials: {},

    config: null,

    init: function(config){
        this.config = config;
    },

    /**
     * Register component view templates as partials.
     * Called every time the component file tree changes.
     */

    registerViews: function(views) {
        var self = this;
        views.forEach(function(view){
            self.partials[view.handle] = view.path;
            if (view.alias) {
                self.partials[view.alias] = view.path;
            }
        });
    },

    /**
     * Render the component view contents.
     * More work than it should be because it needs some skanky
     * partial path rewriting to make them play nicely with consolidate.
     */

    render: function(str, context, meta) {
        context.partials = {};
        var tplPath = meta.path;
        _.each(this.partials, function(partialPath, partialKey){
            if (tplPath != partialPath) {
                var relPath = path.relative(tplPath, partialPath).replace('../', '');
                var parts = path.parse(relPath);
                if ( !_.isEmpty(parts.name) && (path.extname(tplPath) == path.extname(partialPath))) {
                    context.partials[partialKey] = path.join(parts.dir, parts.name);
                }
            }
        });
        return consolidate[this.config.name](meta.path, context);
    }

};
<commit_msg>Tweaks to use with adjusted engine API<commit_after>'use strict';

var consolidate = require('consolidate');
var path        = require('path');
var _           = require('lodash');

module.exports = {

    partials: {},

    defaults: {
        ext: '.hbs',
        name: 'handlebars'
    },

    config: null,

    configure: function(config){
        this.config = config;
    },

    /**
     * Register component view templates as partials.
     * Called every time the component file tree changes.
     */

    registerViews: function(views) {
        var self = this;
        views.forEach(function(view){
            self.partials[view.handle] = view.path;
            if (view.alias) {
                self.partials[view.alias] = view.path;
            }
        });
    },

    /**
     * Render the component view contents.
     * More work than it should be because it needs some skanky
     * partial path rewriting to make them play nicely with consolidate.
     */

    render: function(str, context, meta) {
        context.partials = {};
        var tplPath = meta.path;
        _.each(this.partials, function(partialPath, partialKey){
            if (tplPath != partialPath) {
                var relPath = path.relative(tplPath, partialPath).replace('../', '');
                var parts = path.parse(relPath);
                if ( !_.isEmpty(parts.name) && (path.extname(tplPath) == path.extname(partialPath))) {
                    context.partials[partialKey] = path.join(parts.dir, parts.name);
                }
            }
        });
        return consolidate[this.config.name](meta.path, context);
    }

};
<|endoftext|>