<commit_before>var spawn = require('child_process').spawn,
    path = require('path'),
    fs = require('fs'),
    q = require('q'),
    mustache = require('mustache'),
    utils

utils = module.exports = {
    /**
     * Converts events in dash-delimited format to properties of the form onEventName
     * @param {Array} prefix the prefix of the propified events. Default: on
     * @param {Array} events the events to propify
     * @return {Object} a hash from onEventName to event-name strings
     */
    events2Props: function( prefixesArg, eventsArg ) {
        var prefixes = eventsArg ? prefixesArg : [ 'on' ],
            events = eventsArg ? eventsArg : prefixesArg
        return events.reduce( function( propHash, event ) {
            var eventPropSuffix = event.split('-').map( function( eventIdentifier ) {
                    return eventIdentifier.slice( 0, 1 ).toUpperCase() + eventIdentifier.slice( 1 )
                }).join('')
            prefixes.map( function( prefix ) {
                var eventProp = prefix + eventPropSuffix
                propHash[ eventProp ] = event
            })
            return propHash
        }, {} )
    },

    /**
     * Executes a git command in a specified repo
     * @param {String} repo the path to the repository in which to execute the command
     * @param {String} cmd the git command to run
     * @param {String|Array} args the arguments to pass to the command
     * @return {Promise} a promise on the completion of the command
     */
    git: function( repo, cmd, args ) {
        return q.nfcall( fs.stat, repo )
        .then( function() {
            var done = q.defer(),
                cmdArgs = ( args instanceof Array ? args : args.trim().split(' ') ),
                git = spawn( 'git', [ cmd ].concat( cmdArgs ), { cwd: repo } ),
                output = '',
                errOutput = ''

            git.stderr.on( 'data', function( data ) { errOutput += data.toString() })
            git.stdout.on( 'data', function( data ) { output += data.toString() })

            git.on( 'close', function( code ) {
                if ( code !== 0 ) {
                    done.reject( Error( errOutput ) )
                } else {
                    done.resolve( output )
                }
            })

            return done.promise
        })
    },

    /**
     * Recursively makes directories, a la `mkdir -p`
     * @param {String} dirPath the path to the directory to make
     * @param {String} base the directory in which to create the tree. Default: /
     * @return {Promise}
     */
    mkdirp: function( dirPath, base ) {
        var rel = base || '/',
            dirs = Array.isArray( dirPath ) ? dirPath : dirPath.split( path.sep ),
            theDir = path.join( rel, dirs.shift() || '' ),
            mkChildDir = dirs.length === 0 ? function() {} : utils.mkdirp.bind( null, dirs, theDir )

        return q.nfcall( fs.stat, theDir )
        .then( mkChildDir, function( e ) {
            if ( e.code === 'ENOENT' ) {
                return q.nfcall( fs.mkdir, theDir ).then( mkChildDir )
            }
            throw e
        })
    },

    /**
     * Templates a file and writes it out
     * @param {String} src the path to the source file/dir. Leave blank to create new file.
     * @param {String} dest the path to the output file/dir (only file if `src` is unspecified)
     * @param {Object} template a template object a la mustache
     * @return {Promise}
     */
    writeOut: function( src, dest, template ) {
        if ( !src ) {
            return utils.mkdirp( path.dirname( dest ) )
            .then( q.nfcall.bind( null, fs.writeFile, dest, '' ) )
        }

        return q.nfcall( fs.stat, src )
        .then( function( stats ) {
            var srcPath = path.join.bind( null, src ),
                destPath = path.join.bind( null, dest )

            if ( stats.isDirectory() ) { // recursiely template and write out directory
                return q.nfcall( fs.mkdir, dest ) // make the dest directory
                .then( function() {
                    return q.nfcall( fs.readdir, src ) // recurse into the src dir
                })
                .then( function( dirContents ) {
                    return q.all( dirContents.map( function( file ) {
                        return utils.writeOut( srcPath( file ), destPath( file ), template )
                    }) )
                })
            } else {
                return q.nfcall( fs.readFile, src, { encoding: 'utf8' } )
                .then( function( file ) {
                    var fileTemplate = typeof template === 'function' ? template( src ) : template,
                        templated = mustache.render( file, fileTemplate )
                    return q.nfcall( fs.writeFile, dest, templated )
                })
            }
        })
    },

    /**
     * Adds the specified files (possibly templated) to the given repo and commits them
     * @param {String} repo the path to the repository. Dest files are relative to the repo root
     * @param {String} srcBase path to which src files paths are relative. Default: /
     * @param {Object} spec the specification for the commit
     *  spec: {
     *    msg: String,
     *    author: String, // Default: GitStream <gitstream@csail.mit.edu>
     *    date: Date, // Default: current Date
     *    files: Array[String|Object]
     *      // if String, copies from src to dest. Assumes same directory structure
     *      // if Object, refers to a fileSpec, as described below
     *  }
     *
     *  fileSpec: {
     *    src: String, // path to file relative to `srcBase`. Leave blank when creating new files.
     *      // can be a directory. will be recursively templated/written out
     *    dest: String, // path to destination relative to `repo`. Will recursively create dirs.
     *    template: Object|Function, // a Mustache template object or object-generating function
     *      // the object-generating function receives the source path. noop if src is undefined
     *  }
     * @return {Promise} a promise on the completion of the commands
     */
    addCommit: function( repo, srcBase, spec ) {
        var srcPath = path.join.bind( null, arguments.length < 3 ? '/' : srcBase ),
            destPath = path.join.bind( null, repo ),
            commitAuthor = spec.author || 'GitStream <gitstream@csail.mit.edu>',
            commitDate = ( spec.date || new Date() ).toISOString(),
            commitMsg = spec.msg.replace( /'/g, '\\"' ),
            filesToStage = []

        return q.all( spec.files.map( function( fileSpec ) {
            var srcArg = typeof fileSpec === 'string' ? fileSpec : fileSpec.src,
                src = srcArg ? srcPath( srcArg ) : '',
                dest = typeof fileSpec === 'string' ? fileSpec : fileSpec.dest || fileSpec.src,
                template = typeof fileSpec === 'string' ? {} : fileSpec.template

            filesToStage.push( ':' + dest )

            return utils.writeOut( src, destPath( dest ), template )
        }) )
        .then( function() {
            return utils.git( repo, 'add', filesToStage.join(' ') )
        })
        .then( function() {
            return utils.git( repo, 'commit', [
                '-m', '"' + commitMsg + '"',
                '--author="' + commitAuthor + '"',
                '--date=' + commitDate
            ])
        })
    }
}
<commit_msg>mkdirp when copying from non-dir src to dest<commit_after>var spawn = require('child_process').spawn,
    path = require('path'),
    fs = require('fs'),
    q = require('q'),
    mustache = require('mustache'),
    utils

utils = module.exports = {
    /**
     * Converts events in dash-delimited format to properties of the form onEventName
     * @param {Array} prefix the prefix of the propified events. Default: on
     * @param {Array} events the events to propify
     * @return {Object} a hash from onEventName to event-name strings
     */
    events2Props: function( prefixesArg, eventsArg ) {
        var prefixes = eventsArg ? prefixesArg : [ 'on' ],
            events = eventsArg ? eventsArg : prefixesArg
        return events.reduce( function( propHash, event ) {
            var eventPropSuffix = event.split('-').map( function( eventIdentifier ) {
                    return eventIdentifier.slice( 0, 1 ).toUpperCase() + eventIdentifier.slice( 1 )
                }).join('')
            prefixes.map( function( prefix ) {
                var eventProp = prefix + eventPropSuffix
                propHash[ eventProp ] = event
            })
            return propHash
        }, {} )
    },

    /**
     * Executes a git command in a specified repo
     * @param {String} repo the path to the repository in which to execute the command
     * @param {String} cmd the git command to run
     * @param {String|Array} args the arguments to pass to the command
     * @return {Promise} a promise on the completion of the command
     */
    git: function( repo, cmd, args ) {
        return q.nfcall( fs.stat, repo )
        .then( function() {
            var done = q.defer(),
                cmdArgs = ( args instanceof Array ? args : args.trim().split(' ') ),
                git = spawn( 'git', [ cmd ].concat( cmdArgs ), { cwd: repo } ),
                output = '',
                errOutput = ''

            git.stderr.on( 'data', function( data ) { errOutput += data.toString() })
            git.stdout.on( 'data', function( data ) { output += data.toString() })

            git.on( 'close', function( code ) {
                if ( code !== 0 ) {
                    done.reject( Error( errOutput ) )
                } else {
                    done.resolve( output )
                }
            })

            return done.promise
        })
    },

    /**
     * Recursively makes directories, a la `mkdir -p`
     * @param {String} dirPath the path to the directory to make
     * @param {String} base the directory in which to create the tree. Default: /
     * @return {Promise}
     */
    mkdirp: function( dirPath, base ) {
        var rel = base || '/',
            dirs = Array.isArray( dirPath ) ? dirPath : dirPath.split( path.sep ),
            theDir = path.join( rel, dirs.shift() || '' ),
            mkChildDir = dirs.length === 0 ? function() {} : utils.mkdirp.bind( null, dirs, theDir )

        return q.nfcall( fs.stat, theDir )
        .then( mkChildDir, function( e ) {
            if ( e.code === 'ENOENT' ) {
                return q.nfcall( fs.mkdir, theDir ).then( mkChildDir )
            }
            throw e
        })
    },

    /**
     * Templates a file and writes it out
     * @param {String} src the path to the source file/dir. Leave blank to create new file.
     * @param {String} dest the path to the output file/dir (only file if `src` is unspecified)
     * @param {Object} template a template object a la mustache
     * @return {Promise}
     */
    writeOut: function( src, dest, template ) {
        if ( !src ) {
            return utils.mkdirp( path.dirname( dest ) )
            .then( q.nfcall.bind( null, fs.writeFile, dest, '' ) )
        }

        return q.nfcall( fs.stat, src )
        .then( function( stats ) {
            var srcPath = path.join.bind( null, src ),
                destPath = path.join.bind( null, dest )

            if ( stats.isDirectory() ) { // recursiely template and write out directory
                return q.nfcall( fs.mkdir, dest ) // make the dest directory
                .then( function() {
                    return q.nfcall( fs.readdir, src ) // recurse into the src dir
                })
                .then( function( dirContents ) {
                    return q.all( dirContents.map( function( file ) {
                        return utils.writeOut( srcPath( file ), destPath( file ), template )
                    }) )
                })
            } else {
                return q.nfcall( fs.readFile, src, { encoding: 'utf8' } )
                .then( function( file ) {
                    var fileTemplate = typeof template === 'function' ? template( src ) : template,
                        templated = mustache.render( file, fileTemplate )
                    return utils.mkdirp( path.dirname( dest ) )
                    .then( q.nfcall.bind( null, fs.writeFile, dest, templated ) )
                })
            }
        })
    },

    /**
     * Adds the specified files (possibly templated) to the given repo and commits them
     * @param {String} repo the path to the repository. Dest files are relative to the repo root
     * @param {String} srcBase path to which src files paths are relative. Default: /
     * @param {Object} spec the specification for the commit
     *  spec: {
     *    msg: String,
     *    author: String, // Default: GitStream <gitstream@csail.mit.edu>
     *    date: Date, // Default: current Date
     *    files: Array[String|Object]
     *      // if String, copies from src to dest. Assumes same directory structure
     *      // if Object, refers to a fileSpec, as described below
     *  }
     *
     *  fileSpec: {
     *    src: String, // path to file relative to `srcBase`. Leave blank when creating new files.
     *      // can be a directory. will be recursively templated/written out
     *    dest: String, // path to destination relative to `repo`. Will recursively create dirs.
     *    template: Object|Function, // a Mustache template object or object-generating function
     *      // the object-generating function receives the source path. noop if src is undefined
     *  }
     * @return {Promise} a promise on the completion of the commands
     */
    addCommit: function( repo, srcBase, spec ) {
        var srcPath = path.join.bind( null, arguments.length < 3 ? '/' : srcBase ),
            destPath = path.join.bind( null, repo ),
            commitAuthor = spec.author || 'GitStream <gitstream@csail.mit.edu>',
            commitDate = ( spec.date || new Date() ).toISOString(),
            commitMsg = spec.msg.replace( /'/g, '\\"' ),
            filesToStage = []

        return q.all( spec.files.map( function( fileSpec ) {
            var srcArg = typeof fileSpec === 'string' ? fileSpec : fileSpec.src,
                src = srcArg ? srcPath( srcArg ) : '',
                dest = typeof fileSpec === 'string' ? fileSpec : fileSpec.dest || fileSpec.src,
                template = typeof fileSpec === 'string' ? {} : fileSpec.template

            filesToStage.push( ':' + dest )

            return utils.writeOut( src, destPath( dest ), template )
        }) )
        .then( function() {
            return utils.git( repo, 'add', filesToStage.join(' ') )
        })
        .then( function() {
            return utils.git( repo, 'commit', [
                '-m', '"' + commitMsg + '"',
                '--author="' + commitAuthor + '"',
                '--date=' + commitDate
            ])
        })
    }
}
<|endoftext|>


<commit_before>
var assert = require('assert');
var errors = require('muses-errors');
var service = require('../../lib/');

describe('Method', function() {

  it('should be an function', function() {
    expect(typeof service.Methods.Method).toEqual('function');
  });

  it('should have default options', function() {
    expect(typeof service.Methods.Method.defaultOptions).toEqual('function');
    expect(typeof service.Methods.Method.defaultOptions()).toEqual('object');
  });

  it('should should require a name and an implementation', function() {

    var throwName = function() {
      return new service.Methods.Method();
    };

    var throwImplementation = function() {
      return new service.Methods.Method('test');
    };

    var success = function() {
      return new service.Methods.Method('test', function() {});
    };

    expect(throwName).toThrowError(assert.AssertionError, 'name must be a string');
    expect(throwImplementation).toThrowError(assert.AssertionError, 'implementation must be a function');
    expect(success).not.toThrow();
  });

  describe('name', function() {

    var method = new service.Methods.Method('test', function() {});

    it('should be gettable', function() {
      expect(method.name).toBe('test');
    });

    it('should be settable', function() {
      method.name = 'foo';
      expect(method.name).toBe('foo');
    });

    it('should only allow a string', function() {
      var setNumber = function() {
        method.name = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'name must be a string');
    });
  });

  describe('implementation', function() {
    var fnTruthy = function() { return true; };
    var fnFalsy = function() { return true; };

    var method = new service.Methods.Method('test', fnTruthy);

    it('should be gettable', function() {
      expect(method.implementation).toBe(fnTruthy);
    });

    it('should be settable', function() {
      method.implementation = fnFalsy;
      expect(method.implementation).toBe(fnFalsy);
    });

    it('should only allow a function', function() {
      var setNumber = function() {
        method.implementation = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'implementation must be a function');
    });
  });

  describe('options', function() {
    var optsTruthy = { foo: true };
    var optsFalsy = { foo: false };

    var method = new service.Methods.Method('test', function() {}, optsTruthy);

    it('should be gettable', function() {
      expect(method.options).toEqual(optsTruthy);
    });

    it('should be settable', function() {
      method.options = optsFalsy;
      expect(method.options).toEqual(optsFalsy);
    });

    it('should only allow an object', function() {
      var setNumber = function() {
        method.options = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'options must be an object');
    });

    it('should set method\'s own properties on method', function() {
      var optsWithName = { name: 'renamed' };
      method.options = optsWithName;
      expect(method.name).toEqual('renamed');
      expect(method.options.name).toBeUndefined(undefined);
    });
  });

  describe('service', function() {

    var method = new service.Methods.Method('test', function() {});
    var svc = new service.Service('Service', []);

    it('should be settable', function() {
      method.service = svc;
      expect(method.service).toBe(svc);
    });

    it('should be gettable', function() {
      expect(method.service).toBe(svc);
    });

    it('should only allow a Service', function() {
      var setNumber = function() {
        method.service = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'service must be an instance of Service');
    });
  });

  describe('invoke', function() {

    // Create an echo method.
    var echo = new service.Methods.Method(
      'echo',
      function(text) {
        return text;
      }
    );

    it('should be a function', function() {
      expect(typeof echo.invoke).toEqual('function');
    });

    it('should return a promise', function() {
      expect(echo.invoke('Foo') instanceof Promise).toEqual(true);
    });

    it('should fulfill a promise on success', function(done) {
      echo
        .invoke('Foo')
        .then(function(res) {
          expect(res).toEqual('Foo');
          done();
        })
        .catch(function(error) {
          done.fail('promise rejected');
        });
    });

    it('should reject promise with NotFound error on null', function(done) {

      // Create a notFound method.
      var notFound = new service.Methods.Method(
        'notFound',
        function() {
          return null;
        }
      );

      notFound
        .invoke()
        .then(function(res) {
          done.fail('promise should be rejected');
        })
        .catch(function(error) {
          expect(error instanceof errors.NotFound).toEqual(true);
          done();
        });
    });

    it('should reject a promise on error', function(done) {

      // Create a reject method.
      var reject = new service.Methods.Method(
        'reject',
        function() {
          throw new errors.Error('Nope');
        }
      );

      reject
        .invoke()
        .then(function(res) {
          done.fail('promise should be rejected');
        })
        .catch(function(error) {
          expect(error.message).toEqual('Nope');
          done();
        });
    });

    it('should pass on all arguments plus itself', function(done) {

      // Create a method to count it's arguments.
      var counter = new service.Methods.Method(
        'counter',
        function() {
          return arguments.length;
        }
      );

      counter.invoke(1, 2, 3).then(function(res) {
        expect(res).toEqual(4);
        done();
      });
    });
  });
});
<commit_msg>Added test to ensure Method would assimilate a returned Promise.<commit_after>
var assert = require('assert');
var errors = require('muses-errors');
var service = require('../../lib/');

describe('Method', function() {

  it('should be an function', function() {
    expect(typeof service.Methods.Method).toEqual('function');
  });

  it('should have default options', function() {
    expect(typeof service.Methods.Method.defaultOptions).toEqual('function');
    expect(typeof service.Methods.Method.defaultOptions()).toEqual('object');
  });

  it('should should require a name and an implementation', function() {

    var throwName = function() {
      return new service.Methods.Method();
    };

    var throwImplementation = function() {
      return new service.Methods.Method('test');
    };

    var success = function() {
      return new service.Methods.Method('test', function() {});
    };

    expect(throwName).toThrowError(assert.AssertionError, 'name must be a string');
    expect(throwImplementation).toThrowError(assert.AssertionError, 'implementation must be a function');
    expect(success).not.toThrow();
  });

  describe('name', function() {

    var method = new service.Methods.Method('test', function() {});

    it('should be gettable', function() {
      expect(method.name).toBe('test');
    });

    it('should be settable', function() {
      method.name = 'foo';
      expect(method.name).toBe('foo');
    });

    it('should only allow a string', function() {
      var setNumber = function() {
        method.name = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'name must be a string');
    });
  });

  describe('implementation', function() {
    var fnTruthy = function() { return true; };
    var fnFalsy = function() { return true; };

    var method = new service.Methods.Method('test', fnTruthy);

    it('should be gettable', function() {
      expect(method.implementation).toBe(fnTruthy);
    });

    it('should be settable', function() {
      method.implementation = fnFalsy;
      expect(method.implementation).toBe(fnFalsy);
    });

    it('should only allow a function', function() {
      var setNumber = function() {
        method.implementation = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'implementation must be a function');
    });
  });

  describe('options', function() {
    var optsTruthy = { foo: true };
    var optsFalsy = { foo: false };

    var method = new service.Methods.Method('test', function() {}, optsTruthy);

    it('should be gettable', function() {
      expect(method.options).toEqual(optsTruthy);
    });

    it('should be settable', function() {
      method.options = optsFalsy;
      expect(method.options).toEqual(optsFalsy);
    });

    it('should only allow an object', function() {
      var setNumber = function() {
        method.options = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'options must be an object');
    });

    it('should set method\'s own properties on method', function() {
      var optsWithName = { name: 'renamed' };
      method.options = optsWithName;
      expect(method.name).toEqual('renamed');
      expect(method.options.name).toBeUndefined(undefined);
    });
  });

  describe('service', function() {

    var method = new service.Methods.Method('test', function() {});
    var svc = new service.Service('Service', []);

    it('should be settable', function() {
      method.service = svc;
      expect(method.service).toBe(svc);
    });

    it('should be gettable', function() {
      expect(method.service).toBe(svc);
    });

    it('should only allow a Service', function() {
      var setNumber = function() {
        method.service = 1;
      };
      expect(setNumber).toThrowError(assert.AssertionError, 'service must be an instance of Service');
    });
  });

  describe('invoke', function() {

    // Create an echo method.
    var echo = new service.Methods.Method(
      'echo',
      function(text) {
        return text;
      }
    );

    it('should be a function', function() {
      expect(typeof echo.invoke).toEqual('function');
    });

    it('should return a promise', function() {
      expect(echo.invoke('Foo') instanceof Promise).toEqual(true);
    });

    it('should fulfill a promise on success', function(done) {
      echo
        .invoke('Foo')
        .then(function(res) {
          expect(res).toEqual('Foo');
          done();
        })
        .catch(function(error) {
          done.fail('promise rejected');
        });
    });

    it('should assimilate a returned promise', function(done) {

      var double = new service.Methods.Method(
        'double',
        function(str) {
          return Promise.resolve(str + str);
        }
      );

      double
        .invoke('foo')
        .then(function(res) {
          expect(res).toEqual('foofoo');
          done();
        })
        .catch(function(error) {
          done.fail('promise rejected');
        });
    })

    it('should reject promise with NotFound error on null', function(done) {

      // Create a notFound method.
      var notFound = new service.Methods.Method(
        'notFound',
        function() {
          return null;
        }
      );

      notFound
        .invoke()
        .then(function(res) {
          done.fail('promise should be rejected');
        })
        .catch(function(error) {
          expect(error instanceof errors.NotFound).toEqual(true);
          done();
        });
    });

    it('should reject a promise on error', function(done) {

      // Create a reject method.
      var reject = new service.Methods.Method(
        'reject',
        function() {
          throw new errors.Error('Nope');
        }
      );

      reject
        .invoke()
        .then(function(res) {
          done.fail('promise should be rejected');
        })
        .catch(function(error) {
          expect(error.message).toEqual('Nope');
          done();
        });
    });

    it('should pass on all arguments plus itself', function(done) {

      // Create a method to count it's arguments.
      var counter = new service.Methods.Method(
        'counter',
        function() {
          return arguments.length;
        }
      );

      counter.invoke(1, 2, 3).then(function(res) {
        expect(res).toEqual(4);
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>var inpaintingController = inpaintingController || {};

let canvas, context;
let tool;
let tools = {};
let inpaintingControls = [];
let background;
let nDots;

inpaintingController.init = async (index, blobSrc, canvasElement) => {
  canvas = canvasElement;
  context = canvas.getContext('2d');
  if (ddl.inputs[index].max_dots) nDots = ddl.inputs[index].max_dots;
  if (!inpaintingControls[index]) {
    tool = new tools[ddl.inputs[index].control];
    inpaintingControls[index] = tool;
  } else {
    tool = inpaintingControls[index];
  }

  $(canvas).mousedown(canvas_event);
  $(canvas).mousemove(canvas_event);
  $(canvas).mouseup(canvas_event);
  $(canvas).mouseout(canvas_event);

  $('#color-picker').change(setStyle);
  $('#size-selector').change(setStyle);
  $("#closeFigure").change(tool.draw);
  addEraseEvent();

  if (!background) background = await loadImage(blobSrc);
  canvas.height = background.height;
  canvas.width = background.width;
  setStyle();
  tool.draw();
}

addEraseEvent = () => {
  $('#erase-btn').click(function () {
    if ($(this).hasClass('activated')) {
      $(this).removeClass('activated');
      context.globalCompositeOperation = 'source-over';
    } else {
      $(this).addClass('activated');
      context.globalCompositeOperation = 'destination-out';
    }
  });
}

canvas_event = (ev) => {
  if (ev.layerX || ev.layerX == 0) { // Firefox
    ev._x = ev.layerX;
    ev._y = ev.layerY;
  } else if (ev.offsetX || ev.offsetX == 0) { // Chrome Opera
    ev._x = ev.offsetX;
    ev._y = ev.offsetY;
  }

  // Call the event handler of the tool.
  var tool_handler = tool[ev.type];
  if (tool_handler) {
    tool_handler(ev);
  }
}

function loadImage(src) {
  return new Promise((resolve, reject) => {
    var img = new Image();
    img.onload = () => resolve(img);
    img.src = src;
  });
}

function setStyle() {
  context.strokeStyle = $('#color-picker').val();
  context.lineWidth = $('#size-selector').val();
  context.lineCap = "round";
  context.lineJoin = "round";
  context.save();
}

function resetCanvas() {
  context.beginPath();
  // Use the identity matrix while clearing the canvas
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
}

tools.mask = function () {
  let tool = this;
  this.started = false;
  this.lastX = 0;
  this.lastY = 0;
  let state = [$('#editor-blob-left')[0].toDataURL()];
  let idx = 0;

  this.mousedown = (ev) => {
    tool.started = true;
    tool.drawStroke(ev._x, ev._y, false);
  }

  this.mousemove = (ev) => {
    if (tool.started) {
      tool.drawStroke(ev._x, ev._y, true);
    }
  }

  this.mouseup = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.mouseout = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.drawStroke = (x, y, started) => {
    if (started) {
      context.beginPath();
      context.moveTo(tool.lastX, tool.lastY);
      context.lineTo(x, y);
      context.stroke();
    }
    tool.lastX = x;
    tool.lastY = y;
  }

  this.push = () => {
    if (state.length > idx + 1) state.splice(idx + 1);
    idx++;
    state.push($('#editor-blob-left')[0].toDataURL());
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    var canvasPic = new Image();
    canvasPic.src = state[idx];
    canvasPic.onload = function() {
      resetCanvas();
      context.drawImage(canvasPic, 0, 0);
    }
  }

  this.getData = () => {
    let dataURL = canvas.toDataURL();
    return dataURLtoBlob(dataURL);
  }

  this.clear = ev => {
    resetCanvas();
    tool.push();
  }
};

tools.lines = function () {
  var tool = this;
  let backwardsPoints = [];
  let forwardsPoints = [];
  let history = [];
  this.mousedown = (ev) => {
    backwardsPoints.push([ev._x, ev._y]);
    tool.draw();
    forwardsPoints.length = 0;
  }

  this.undo = () => {
    if (backwardsPoints.length) {
      var lastPoint = backwardsPoints.pop();
      tool.draw();
      forwardsPoints.push(lastPoint);
    }
  }

  this.redo = () => {
    if (forwardsPoints.length > 0) {
      backwardsPoints.push(forwardsPoints.pop());
      tool.draw();
    }
  }

  this.draw = () => {
    resetCanvas();
    context.beginPath();
    if (backwardsPoints.length > nDots) {
      backwardsPoints.shift();
    }
    for (const point of backwardsPoints) {
      context.lineTo(point[0], point[1]);
    }
    if ($("#closeFigure").prop('checked') && backwardsPoints[0]) {
      var firstPoint = backwardsPoints[0];
      context.lineTo(firstPoint[0], firstPoint[1]);
    }
    console.log(backwardsPoints);
    context.stroke();
  }

  this.clear = ev => {
    backwardsPoints.length = 0;
    forwardsPoints.length = 0;
    resetCanvas();
  }

  this.getData = index => {
    return backwardsPoints.slice(backwardsPoints.length - nDots, backwardsPoints.length);
  }

  this.push = () => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

tools.dots = function () {
  var tool = this;
  let backwardsPoints = [];
  let forwardsPoints = [];

  this.mousedown = (ev) => {
    if (backwardsPoints.length >= nDots) backwardsPoints.shift();
    backwardsPoints.push([ev._x, ev._y]);
    tool.draw();
    forwardsPoints.length = 0;
  }

  this.undo = () => {
    if (backwardsPoints.length) {
      var lastPoint = backwardsPoints.pop();
      tool.draw();
      forwardsPoints.push(lastPoint);
    }
  }

  this.redo = () => {
    if (forwardsPoints.length > 0) {
      backwardsPoints.push(forwardsPoints.pop());
      tool.draw();
    }
  }

  this.draw = () => {
    resetCanvas();
    context.beginPath();
    for (const point of backwardsPoints) {
      context.moveTo(point[0], point[1]);
      context.lineTo(point[0], point[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    backwardsPoints.length = 0;
    forwardsPoints.length = 0;
    resetCanvas();
  }

  this.getData = index => {
    return backwardsPoints.slice(backwardsPoints.length - nDots, backwardsPoints.length);
  }

  this.push = ev => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

function dataURLtoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0)
    // Decode base-64 encoded string
    byteString = atob(dataURI.split(',')[1]);

  // separate out the mime component
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // write the bytes of the string to a typed array
  let dataArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    dataArray[i] = byteString.charCodeAt(i);
  }

  return new Blob([dataArray], { type: mimeString });
}
<commit_msg>[ClientApp/inpainting] clearing won't remove previous states on lines control<commit_after>var inpaintingController = inpaintingController || {};

let canvas, context;
let tool;
let tools = {};
let inpaintingControls = [];
let background;
let nDots;

inpaintingController.init = async (index, blobSrc, canvasElement) => {
  canvas = canvasElement;
  context = canvas.getContext('2d');
  if (ddl.inputs[index].max_dots) nDots = ddl.inputs[index].max_dots;
  if (!inpaintingControls[index]) {
    tool = new tools[ddl.inputs[index].control];
    inpaintingControls[index] = tool;
  } else {
    tool = inpaintingControls[index];
  }

  $(canvas).mousedown(canvas_event);
  $(canvas).mousemove(canvas_event);
  $(canvas).mouseup(canvas_event);
  $(canvas).mouseout(canvas_event);

  $('#color-picker').change(setStyle);
  $('#size-selector').change(setStyle);
  $("#closeFigure").change(tool.draw);
  addEraseEvent();

  background = await loadImage(blobSrc);
  canvas.height = background.height;
  canvas.width = background.width;
  setStyle();
  tool.draw();
}

addEraseEvent = () => {
  $('#erase-btn').click(function () {
    if ($(this).hasClass('activated')) {
      $(this).removeClass('activated');
      context.globalCompositeOperation = 'source-over';
    } else {
      $(this).addClass('activated');
      context.globalCompositeOperation = 'destination-out';
    }
  });
}

canvas_event = (ev) => {
  if (ev.layerX || ev.layerX == 0) { // Firefox
    ev._x = ev.layerX;
    ev._y = ev.layerY;
  } else if (ev.offsetX || ev.offsetX == 0) { // Chrome Opera
    ev._x = ev.offsetX;
    ev._y = ev.offsetY;
  }

  // Call the event handler of the tool.
  var tool_handler = tool[ev.type];
  if (tool_handler) {
    tool_handler(ev);
  }
}

function loadImage(src) {
  return new Promise((resolve, reject) => {
    var img = new Image();
    img.onload = () => resolve(img);
    img.src = src;
  });
}

function setStyle() {
  context.strokeStyle = $('#color-picker').val();
  context.lineWidth = $('#size-selector').val();
  context.lineCap = "round";
  context.lineJoin = "round";
  context.save();
}

function resetCanvas() {
  context.beginPath();
  // Use the identity matrix while clearing the canvas
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
}

tools.mask = function () {
  let tool = this;
  this.started = false;
  this.lastX = 0;
  this.lastY = 0;
  let state = [$('#editor-blob-left')[0].toDataURL()];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) state.splice(idx + 1);
    tool.started = true;
    tool.drawStroke(ev._x, ev._y, false);
  }

  this.mousemove = (ev) => {
    if (tool.started) {
      tool.drawStroke(ev._x, ev._y, true);
    }
  }

  this.mouseup = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.mouseout = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.drawStroke = (x, y, started) => {
    if (started) {
      context.beginPath();
      context.moveTo(tool.lastX, tool.lastY);
      context.lineTo(x, y);
      context.stroke();
    }
    tool.lastX = x;
    tool.lastY = y;
  }

  this.push = () => {
    idx++;
    state.push($('#editor-blob-left')[0].toDataURL());
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    var canvasPic = new Image();
    canvasPic.src = state[idx];
    canvasPic.onload = function() {
      resetCanvas();
      context.drawImage(canvasPic, 0, 0);
    }
  }

  this.getData = () => {
    let dataURL = canvas.toDataURL();
    return dataURLtoBlob(dataURL);
  }

  this.clear = ev => {
    resetCanvas();
    tool.push();
  }
};

tools.lines = function () {
  var tool = this;
  let currentState = [];
  let state = [currentState.map(x => ([...x]))];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    currentState.push([ev._x, ev._y]);
    if (currentState.length > nDots) {
      currentState.shift();
    }
    state.push(currentState.map(x => ([...x])));
    idx++;
    tool.draw();
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    if (state[idx]) currentState = state[idx].map(x => ([...x]));
    resetCanvas();
    context.beginPath();
    for (const point of currentState) {
      context.lineTo(point[0], point[1]);
    }
    if ($("#closeFigure").prop('checked') && currentState[0]) {
      var firstPoint = currentState[0];
      context.lineTo(firstPoint[0], firstPoint[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    state.push([]);
    idx++;
    resetCanvas();
  }

  this.getData = index => {
    return state.slice(state.length - nDots, state.length);
  }

  this.push = () => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

tools.dots = function () {
  var tool = this;
  let backwardsPoints = [];
  let forwardsPoints = [];

  this.mousedown = (ev) => {
    if (backwardsPoints.length >= nDots) backwardsPoints.shift();
    backwardsPoints.push([ev._x, ev._y]);
    tool.draw();
    forwardsPoints.length = 0;
  }

  this.undo = () => {
    if (backwardsPoints.length) {
      var lastPoint = backwardsPoints.pop();
      tool.draw();
      forwardsPoints.push(lastPoint);
    }
  }

  this.redo = () => {
    if (forwardsPoints.length > 0) {
      backwardsPoints.push(forwardsPoints.pop());
      tool.draw();
    }
  }

  this.draw = () => {
    resetCanvas();
    context.beginPath();
    for (const point of backwardsPoints) {
      context.moveTo(point[0], point[1]);
      context.lineTo(point[0], point[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    backwardsPoints.length = 0;
    forwardsPoints.length = 0;
    resetCanvas();
  }

  this.getData = index => {
    return backwardsPoints.slice(backwardsPoints.length - nDots, backwardsPoints.length);
  }

  this.push = ev => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

function dataURLtoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0)
    // Decode base-64 encoded string
    byteString = atob(dataURI.split(',')[1]);

  // separate out the mime component
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // write the bytes of the string to a typed array
  let dataArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    dataArray[i] = byteString.charCodeAt(i);
  }

  return new Blob([dataArray], { type: mimeString });
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');
const pluralize = require('pluralize');

/*
 * Set of utils for models
 */
module.exports = {
  /**
   * Initialize to prevent some mistakes
   */

  initialize: cb => {
    cb();
  },

  /**
   * Retrieve the value based on the primary key
   */

  getValuePrimaryKey: (value, defaultKey) => {
    return value[defaultKey] || value.id || value._id;
  },

  /**
   * Find relation nature with verbose
   */

  getNature: ({ attribute, attributeName, modelName }) => {
    const types = {
      current: '',
      other: '',
    };

    const models = strapi.db.getModelsByPluginName(attribute.plugin);

    const pluginModels = Object.values(strapi.plugins).reduce((acc, plugin) => {
      return acc.concat(Object.values(plugin.models));
    }, []);

    const allModels = Object.values(strapi.models).concat(pluginModels);

    if (
      (_.has(attribute, 'collection') && attribute.collection === '*') ||
      (_.has(attribute, 'model') && attribute.model === '*')
    ) {
      if (attribute.model) {
        types.current = 'morphToD';
      } else {
        types.current = 'morphTo';
      }

      // We have to find if they are a model linked to this key
      _.forEach(allModels, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    } else if (_.has(attribute, 'via') && _.has(attribute, 'collection')) {
      const relatedAttribute = models[attribute.collection].attributes[attribute.via];

      if (!relatedAttribute) {
        throw new Error(
          `The attribute \`${attribute.via}\` is missing in the model ${_.upperFirst(
            attribute.collection
          )} ${attribute.plugin ? '(plugin - ' + attribute.plugin + ')' : ''}`
        );
      }

      types.current = 'collection';

      if (
        _.has(relatedAttribute, 'collection') &&
        relatedAttribute.collection !== '*' &&
        _.has(relatedAttribute, 'via')
      ) {
        types.other = 'collection';
      } else if (
        _.has(relatedAttribute, 'collection') &&
        relatedAttribute.collection !== '*' &&
        !_.has(relatedAttribute, 'via')
      ) {
        types.other = 'collectionD';
      } else if (_.has(relatedAttribute, 'model') && relatedAttribute.model !== '*') {
        types.other = 'model';
      } else if (_.has(relatedAttribute, 'collection') || _.has(relatedAttribute, 'model')) {
        types.other = 'morphTo';
      }
    } else if (_.has(attribute, 'via') && _.has(attribute, 'model')) {
      types.current = 'modelD';

      // We have to find if they are a model linked to this attributeName
      const model = models[attribute.model];

      const reverseAttribute = model.attributes[attribute.via];

      if (
        _.has(reverseAttribute, 'via') &&
        reverseAttribute.via === attributeName &&
        _.has(reverseAttribute, 'collection') &&
        reverseAttribute.collection !== '*'
      ) {
        types.other = 'collection';
      } else if (_.has(reverseAttribute, 'model') && reverseAttribute.model !== '*') {
        types.other = 'model';
      } else if (_.has(reverseAttribute, 'collection') || _.has(reverseAttribute, 'model')) {
        types.other = 'morphTo';
      }
    } else if (_.has(attribute, 'model')) {
      types.current = 'model';

      // We have to find if they are a model linked to this attributeName
      _.forIn(models, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    } else if (_.has(attribute, 'collection')) {
      types.current = 'collectionD';

      // We have to find if they are a model linked to this attributeName
      _.forIn(models, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    }

    if (types.current === 'collection' && types.other === 'morphTo') {
      return {
        nature: 'manyToManyMorph',
        verbose: 'morphMany',
      };
    } else if (types.current === 'collection' && types.other === 'morphToD') {
      return {
        nature: 'manyToOneMorph',
        verbose: 'morphMany',
      };
    } else if (types.current === 'modelD' && types.other === 'morphTo') {
      return {
        nature: 'oneToManyMorph',
        verbose: 'morphOne',
      };
    } else if (types.current === 'modelD' && types.other === 'morphToD') {
      return {
        nature: 'oneToOneMorph',
        verbose: 'morphOne',
      };
    } else if (types.current === 'morphToD' && types.other === 'collection') {
      return {
        nature: 'oneMorphToMany',
        verbose: 'belongsToMorph',
      };
    } else if (types.current === 'morphToD' && types.other === 'model') {
      return {
        nature: 'oneMorphToOne',
        verbose: 'belongsToMorph',
      };
    } else if (
      types.current === 'morphTo' &&
      (types.other === 'model' || _.has(attribute, 'model'))
    ) {
      return {
        nature: 'manyMorphToOne',
        verbose: 'belongsToManyMorph',
      };
    } else if (
      types.current === 'morphTo' &&
      (types.other === 'collection' || _.has(attribute, 'collection'))
    ) {
      return {
        nature: 'manyMorphToMany',
        verbose: 'belongsToManyMorph',
      };
    } else if (types.current === 'modelD' && types.other === 'model') {
      return {
        nature: 'oneToOne',
        verbose: 'belongsTo',
      };
    } else if (types.current === 'model' && types.other === 'modelD') {
      return {
        nature: 'oneToOne',
        verbose: 'hasOne',
      };
    } else if (
      (types.current === 'model' || types.current === 'modelD') &&
      types.other === 'collection'
    ) {
      return {
        nature: 'manyToOne',
        verbose: 'belongsTo',
      };
    } else if (types.current === 'modelD' && types.other === 'collection') {
      return {
        nature: 'oneToMany',
        verbose: 'hasMany',
      };
    } else if (types.current === 'collection' && types.other === 'model') {
      return {
        nature: 'oneToMany',
        verbose: 'hasMany',
      };
    } else if (types.current === 'collection' && types.other === 'collection') {
      return {
        nature: 'manyToMany',
        verbose: 'belongsToMany',
      };
    } else if (
      (types.current === 'collectionD' && types.other === 'collection') ||
      (types.current === 'collection' && types.other === 'collectionD')
    ) {
      return {
        nature: 'manyToMany',
        verbose: 'belongsToMany',
      };
    } else if (types.current === 'collectionD' && types.other === '') {
      return {
        nature: 'manyWay',
        verbose: 'belongsToMany',
      };
    } else if (types.current === 'model' && types.other === '') {
      return {
        nature: 'oneWay',
        verbose: 'belongsTo',
      };
    }

    return undefined;
  },

  /**
   * Return table name for a collection many-to-many
   */
  getCollectionName: (associationA, associationB) => {
    if (associationA.dominant && _.has(associationA, 'collectionName')) {
      return associationA.collectionName;
    }

    if (associationB.dominant && _.has(associationB, 'collectionName')) {
      return associationB.collectionName;
    }

    return [associationA, associationB]
      .sort((a, b) => {
        if (a.collection === b.collection) {
          if (a.dominant) return 1;
          else return -1;
        }
        return a.collection < b.collection ? -1 : 1;
      })
      .map(table =>
        _.snakeCase(`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`)
      )
      .join('__');
  },

  /**
   * Define associations key to models
   */

  defineAssociations: function(model, definition, association, key) {
    try {
      // Initialize associations object
      if (definition.associations === undefined) {
        definition.associations = [];
      }

      // Exclude non-relational attribute
      if (!_.has(association, 'collection') && !_.has(association, 'model')) {
        return;
      }

      // Get relation nature
      let details;
      const targetName = association.model || association.collection || '';
      const infos = this.getNature({
        attribute: association,
        attributeName: key,
        modelName: model.toLowerCase(),
      });

      if (targetName !== '*') {
        const model = strapi.db.getModel(targetName, association.plugin);
        details = _.get(model, ['attributes', association.via], {});
      }

      // Build associations object
      if (_.has(association, 'collection') && association.collection !== '*') {
        const ast = {
          alias: key,
          type: 'collection',
          collection: association.collection,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        };

        if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
          ast.tableCollectionName = this.getCollectionName(details, association);
        }

        if (infos.nature === 'manyWay' && definition.orm === 'bookshelf') {
          ast.tableCollectionName =
            _.get(association, 'collectionName') ||
            `${definition.collectionName}__${_.snakeCase(key)}`;
        }
        definition.associations.push(ast);
        return;
      }

      if (_.has(association, 'model') && association.model !== '*') {
        definition.associations.push({
          alias: key,
          type: 'model',
          model: association.model,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
        return;
      }

      const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
        Object.keys(strapi.plugins[current].models).forEach(entity => {
          Object.keys(strapi.plugins[current].models[entity].attributes).forEach(attribute => {
            const attr = strapi.plugins[current].models[entity].attributes[attribute];

            if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
              acc.push(strapi.plugins[current].models[entity].globalId);
            }
          });
        });

        return acc;
      }, []);

      const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
        Object.keys(strapi.models[entity].attributes).forEach(attribute => {
          const attr = strapi.models[entity].attributes[attribute];

          if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
            acc.push(strapi.models[entity].globalId);
          }
        });

        return acc;
      }, []);

      const componentModels = Object.keys(strapi.components).reduce((acc, entity) => {
        Object.keys(strapi.components[entity].attributes).forEach(attribute => {
          const attr = strapi.components[entity].attributes[attribute];

          if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
            acc.push(strapi.components[entity].globalId);
          }
        });

        return acc;
      }, []);

      const models = _.uniq(appModels.concat(pluginsModels).concat(componentModels));

      definition.associations.push({
        alias: key,
        type: association.model ? 'model' : 'collection',
        related: models,
        nature: infos.nature,
        autoPopulate: _.get(association, 'autoPopulate', true),
        filter: association.filter,
      });
    } catch (e) {
      strapi.log.error(
        `Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``
      );
      strapi.log.error(e);
      strapi.stop();
    }
  },
};
<commit_msg>better error handling for missing model error (#8328)<commit_after>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');
const pluralize = require('pluralize');

/*
 * Set of utils for models
 */
module.exports = {
  /**
   * Initialize to prevent some mistakes
   */

  initialize: cb => {
    cb();
  },

  /**
   * Retrieve the value based on the primary key
   */

  getValuePrimaryKey: (value, defaultKey) => {
    return value[defaultKey] || value.id || value._id;
  },

  /**
   * Find relation nature with verbose
   */

  getNature: ({ attribute, attributeName, modelName }) => {
    const types = {
      current: '',
      other: '',
    };

    const models = strapi.db.getModelsByPluginName(attribute.plugin);

    const pluginModels = Object.values(strapi.plugins).reduce((acc, plugin) => {
      return acc.concat(Object.values(plugin.models));
    }, []);

    const allModels = Object.values(strapi.models).concat(pluginModels);

    if (
      (_.has(attribute, 'collection') && attribute.collection === '*') ||
      (_.has(attribute, 'model') && attribute.model === '*')
    ) {
      if (attribute.model) {
        types.current = 'morphToD';
      } else {
        types.current = 'morphTo';
      }

      // We have to find if they are a model linked to this key
      _.forEach(allModels, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    } else if (_.has(attribute, 'via') && _.has(attribute, 'collection')) {
      if (!_.has(models, attribute.collection)) {
        throw new Error(
          `The collection \`${_.upperFirst(
            attribute.collection
          )}\` is missing from the ${attribute.plugin ? '(plugin - ' + attribute.plugin + ')' : ''} models`
        );
      }
      const relatedAttribute = models[attribute.collection].attributes[attribute.via];

      if (!relatedAttribute) {
        throw new Error(
          `The attribute \`${attribute.via}\` is missing in the model ${_.upperFirst(
            attribute.collection
          )} ${attribute.plugin ? '(plugin - ' + attribute.plugin + ')' : ''}`
        );
      }

      types.current = 'collection';

      if (
        _.has(relatedAttribute, 'collection') &&
        relatedAttribute.collection !== '*' &&
        _.has(relatedAttribute, 'via')
      ) {
        types.other = 'collection';
      } else if (
        _.has(relatedAttribute, 'collection') &&
        relatedAttribute.collection !== '*' &&
        !_.has(relatedAttribute, 'via')
      ) {
        types.other = 'collectionD';
      } else if (_.has(relatedAttribute, 'model') && relatedAttribute.model !== '*') {
        types.other = 'model';
      } else if (_.has(relatedAttribute, 'collection') || _.has(relatedAttribute, 'model')) {
        types.other = 'morphTo';
      }
    } else if (_.has(attribute, 'via') && _.has(attribute, 'model')) {
      types.current = 'modelD';

      // We have to find if they are a model linked to this attributeName
      const model = models[attribute.model];

      const reverseAttribute = model.attributes[attribute.via];

      if (
        _.has(reverseAttribute, 'via') &&
        reverseAttribute.via === attributeName &&
        _.has(reverseAttribute, 'collection') &&
        reverseAttribute.collection !== '*'
      ) {
        types.other = 'collection';
      } else if (_.has(reverseAttribute, 'model') && reverseAttribute.model !== '*') {
        types.other = 'model';
      } else if (_.has(reverseAttribute, 'collection') || _.has(reverseAttribute, 'model')) {
        types.other = 'morphTo';
      }
    } else if (_.has(attribute, 'model')) {
      types.current = 'model';

      // We have to find if they are a model linked to this attributeName
      _.forIn(models, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    } else if (_.has(attribute, 'collection')) {
      types.current = 'collectionD';

      // We have to find if they are a model linked to this attributeName
      _.forIn(models, model => {
        _.forIn(model.attributes, attribute => {
          if (_.has(attribute, 'via') && attribute.via === attributeName) {
            if (_.has(attribute, 'collection') && attribute.collection === modelName) {
              types.other = 'collection';

              // Break loop
              return false;
            } else if (_.has(attribute, 'model') && attribute.model === modelName) {
              types.other = 'modelD';

              // Break loop
              return false;
            }
          }
        });
      });
    }

    if (types.current === 'collection' && types.other === 'morphTo') {
      return {
        nature: 'manyToManyMorph',
        verbose: 'morphMany',
      };
    } else if (types.current === 'collection' && types.other === 'morphToD') {
      return {
        nature: 'manyToOneMorph',
        verbose: 'morphMany',
      };
    } else if (types.current === 'modelD' && types.other === 'morphTo') {
      return {
        nature: 'oneToManyMorph',
        verbose: 'morphOne',
      };
    } else if (types.current === 'modelD' && types.other === 'morphToD') {
      return {
        nature: 'oneToOneMorph',
        verbose: 'morphOne',
      };
    } else if (types.current === 'morphToD' && types.other === 'collection') {
      return {
        nature: 'oneMorphToMany',
        verbose: 'belongsToMorph',
      };
    } else if (types.current === 'morphToD' && types.other === 'model') {
      return {
        nature: 'oneMorphToOne',
        verbose: 'belongsToMorph',
      };
    } else if (
      types.current === 'morphTo' &&
      (types.other === 'model' || _.has(attribute, 'model'))
    ) {
      return {
        nature: 'manyMorphToOne',
        verbose: 'belongsToManyMorph',
      };
    } else if (
      types.current === 'morphTo' &&
      (types.other === 'collection' || _.has(attribute, 'collection'))
    ) {
      return {
        nature: 'manyMorphToMany',
        verbose: 'belongsToManyMorph',
      };
    } else if (types.current === 'modelD' && types.other === 'model') {
      return {
        nature: 'oneToOne',
        verbose: 'belongsTo',
      };
    } else if (types.current === 'model' && types.other === 'modelD') {
      return {
        nature: 'oneToOne',
        verbose: 'hasOne',
      };
    } else if (
      (types.current === 'model' || types.current === 'modelD') &&
      types.other === 'collection'
    ) {
      return {
        nature: 'manyToOne',
        verbose: 'belongsTo',
      };
    } else if (types.current === 'modelD' && types.other === 'collection') {
      return {
        nature: 'oneToMany',
        verbose: 'hasMany',
      };
    } else if (types.current === 'collection' && types.other === 'model') {
      return {
        nature: 'oneToMany',
        verbose: 'hasMany',
      };
    } else if (types.current === 'collection' && types.other === 'collection') {
      return {
        nature: 'manyToMany',
        verbose: 'belongsToMany',
      };
    } else if (
      (types.current === 'collectionD' && types.other === 'collection') ||
      (types.current === 'collection' && types.other === 'collectionD')
    ) {
      return {
        nature: 'manyToMany',
        verbose: 'belongsToMany',
      };
    } else if (types.current === 'collectionD' && types.other === '') {
      return {
        nature: 'manyWay',
        verbose: 'belongsToMany',
      };
    } else if (types.current === 'model' && types.other === '') {
      return {
        nature: 'oneWay',
        verbose: 'belongsTo',
      };
    }

    return undefined;
  },

  /**
   * Return table name for a collection many-to-many
   */
  getCollectionName: (associationA, associationB) => {
    if (associationA.dominant && _.has(associationA, 'collectionName')) {
      return associationA.collectionName;
    }

    if (associationB.dominant && _.has(associationB, 'collectionName')) {
      return associationB.collectionName;
    }

    return [associationA, associationB]
      .sort((a, b) => {
        if (a.collection === b.collection) {
          if (a.dominant) return 1;
          else return -1;
        }
        return a.collection < b.collection ? -1 : 1;
      })
      .map(table =>
        _.snakeCase(`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`)
      )
      .join('__');
  },

  /**
   * Define associations key to models
   */

  defineAssociations: function(model, definition, association, key) {
    try {
      // Initialize associations object
      if (definition.associations === undefined) {
        definition.associations = [];
      }

      // Exclude non-relational attribute
      if (!_.has(association, 'collection') && !_.has(association, 'model')) {
        return;
      }

      // Get relation nature
      let details;
      const targetName = association.model || association.collection || '';
      const infos = this.getNature({
        attribute: association,
        attributeName: key,
        modelName: model.toLowerCase(),
      });

      if (targetName !== '*') {
        const model = strapi.db.getModel(targetName, association.plugin);
        details = _.get(model, ['attributes', association.via], {});
      }

      // Build associations object
      if (_.has(association, 'collection') && association.collection !== '*') {
        const ast = {
          alias: key,
          type: 'collection',
          collection: association.collection,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        };

        if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
          ast.tableCollectionName = this.getCollectionName(details, association);
        }

        if (infos.nature === 'manyWay' && definition.orm === 'bookshelf') {
          ast.tableCollectionName =
            _.get(association, 'collectionName') ||
            `${definition.collectionName}__${_.snakeCase(key)}`;
        }
        definition.associations.push(ast);
        return;
      }

      if (_.has(association, 'model') && association.model !== '*') {
        definition.associations.push({
          alias: key,
          type: 'model',
          model: association.model,
          via: association.via || undefined,
          nature: infos.nature,
          autoPopulate: _.get(association, 'autoPopulate', true),
          dominant: details.dominant !== true,
          plugin: association.plugin || undefined,
          filter: details.filter,
        });
        return;
      }

      const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
        Object.keys(strapi.plugins[current].models).forEach(entity => {
          Object.keys(strapi.plugins[current].models[entity].attributes).forEach(attribute => {
            const attr = strapi.plugins[current].models[entity].attributes[attribute];

            if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
              acc.push(strapi.plugins[current].models[entity].globalId);
            }
          });
        });

        return acc;
      }, []);

      const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
        Object.keys(strapi.models[entity].attributes).forEach(attribute => {
          const attr = strapi.models[entity].attributes[attribute];

          if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
            acc.push(strapi.models[entity].globalId);
          }
        });

        return acc;
      }, []);

      const componentModels = Object.keys(strapi.components).reduce((acc, entity) => {
        Object.keys(strapi.components[entity].attributes).forEach(attribute => {
          const attr = strapi.components[entity].attributes[attribute];

          if ((attr.collection || attr.model || '').toLowerCase() === model.toLowerCase()) {
            acc.push(strapi.components[entity].globalId);
          }
        });

        return acc;
      }, []);

      const models = _.uniq(appModels.concat(pluginsModels).concat(componentModels));

      definition.associations.push({
        alias: key,
        type: association.model ? 'model' : 'collection',
        related: models,
        nature: infos.nature,
        autoPopulate: _.get(association, 'autoPopulate', true),
        filter: association.filter,
      });
    } catch (e) {
      strapi.log.error(
        `Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``
      );
      strapi.log.error(e);
      strapi.stop();
    }
  },
};
<|endoftext|>


<commit_before>(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;

  var ProfileDetails = window.shared.ProfileDetails = React.createClass({
    displayName: 'ProfileDetails',

    propTypes: {
        student: React.PropTypes.object,
        chartData: React.PropTypes.object,
        attendanceData: React.PropTypes.object
    },

    getEvents: function(){
      // Returns a list of {type: ..., date: ..., value: ...} pairs, sorted by date of occurrence.
      var name = this.props.student.first_name;
      var events = [];

      _.each(this.props.attendanceData.tardies, function(obj){
        events.push({
          message: name + ' was tardy.', date: Date.parse(obj.occurred_at)
        });
      });
      _.each(this.props.attendanceData.absences, function(obj){
        events.push({
          message: name + ' was absent.', date: Date.parse(obj.occurred_at)
        });
      });
      _.each(this.props.chartData.mcas_series_ela_scaled, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + ' on the ELA section of the MCAS.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.mcas_series_math_scaled, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + ' on the Math section of the MCAS.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.star_series_reading_percentile, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + '% on the Reading section of STAR.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.star_series_math_percentile, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + '% on the Math section of STAR.',
          date: new Date(year, month, day)
        });
      });
      // TODO: incorporate discipline incidents.
      return _.sortBy(events, 'date').reverse();
    },

    render: function() {
      return dom.div({},
        dom.ul({},
          this.getEvents().map(function(obj){
            return dom.li({},
              moment(obj.date).format("MMMM Do, YYYY") + ": " + obj.message
            );
          })
        )
      );
    }
  });
})();<commit_msg>Fix React warning on profile timeline list<commit_after>(function() {
  window.shared || (window.shared = {});
  var dom = window.shared.ReactHelpers.dom;
  var createEl = window.shared.ReactHelpers.createEl;
  var merge = window.shared.ReactHelpers.merge;

  var ProfileDetails = window.shared.ProfileDetails = React.createClass({
    displayName: 'ProfileDetails',

    propTypes: {
        student: React.PropTypes.object,
        chartData: React.PropTypes.object,
        attendanceData: React.PropTypes.object
    },

    getEvents: function(){
      // Returns a list of {type: ..., date: ..., value: ...} pairs, sorted by date of occurrence.
      var name = this.props.student.first_name;
      var events = [];

      _.each(this.props.attendanceData.tardies, function(obj){
        events.push({
          message: name + ' was tardy.', date: Date.parse(obj.occurred_at)
        });
      });
      _.each(this.props.attendanceData.absences, function(obj){
        events.push({
          message: name + ' was absent.', date: Date.parse(obj.occurred_at)
        });
      });
      _.each(this.props.chartData.mcas_series_ela_scaled, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + ' on the ELA section of the MCAS.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.mcas_series_math_scaled, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + ' on the Math section of the MCAS.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.star_series_reading_percentile, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + '% on the Reading section of STAR.',
          date: new Date(year, month, day)
        });
      });
      _.each(this.props.chartData.star_series_math_percentile, function(quad){
        var year = quad[0], month = quad[1], day = quad[2], score = quad[3];
        events.push({
          message: name + ' scored a ' + score + '% on the Math section of STAR.',
          date: new Date(year, month, day)
        });
      });
      // TODO: incorporate discipline incidents.
      return _.sortBy(events, 'date').reverse();
    },

    render: function() {
      return dom.div({},
        dom.ul({},
          this.getEvents().map(function(obj){
            var key = [obj.date, obj.message].join();
            return dom.li({ key: key },
              moment(obj.date).format("MMMM Do, YYYY") + ": " + obj.message
            );
          })
        )
      );
    }
  });
})();<|endoftext|>


<commit_before>/**
 * Welcome to Pebble.js!
 * Test
 * This is where you write your app.
 */

// Calls in required stuff
var UI = require('ui');
var Vibe = require('ui/vibe');
var Vector2 = require('vector2');

// A for loop to make array of hours
var hours = [];
for (var i = 0; i < 24; i++) {
    hours.push(i + 1);
}

// Creates a loop to make an array of hours
var createItems = function() {
  var items = [];
  for (var i = 0; i < hours.length; i++) {
    items.push({
      title: hours[i]
    });
  }
  return items;
};

/*
var main = new UI.Menu({
   sections:[{
     items: createItems()
  }]
});

main.show();
*/

/*
 * var main = new UI.Card({
 *   title: 'Pebble.js',
 *     icon: 'images/menu_icon.png',
 *       subtitle: 'Hello World!',
 *         body: 'Press any button.'
 *         });
 */

// Makes a menu with New Alarms
var main = new UI.Menu({
    sections: [{
        items: [{
            title: 'Add New Alarm',
            subtitle: 'New Alarm'
        }, {
            // Dr. Seuss
            title: 'One Alarm'
        }, {
            title: 'Two Alarm'
        }, {
            title: 'Red Alarm'
        }, {
            title: 'Blue Alarm'
        }]
    }]
});

main.show();

main.on('click', 'select', function(e) {
    if () {
    }
});

/*
main.on('click', 'up', function(e) {
    var menu = new UI.Menu({
        sections: [{
            items: [{
                title: 'Pebble.js',
                icon: 'images/menu_icon.png',
                subtitle: 'Can do Menus'
            }, {
                title: 'Second Item',
                subtitle: 'Subtitle Text'
            }]
        }]
    });
    menu.on('select', function(e) {
        console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex);
        console.log('The item is titled "' + e.item.title + '"');
    });
    menu.show();
});
*/

// When click middle button
main.on('click', 'up', function(e) {
    //test vibrqtion and date fubction
    console.log("foo");
    if (new Date() == new Date()) {
        Vibe.vibrate('long');
    }
    var wind = new UI.Window();
    var time = new UI.TimeText();
    // Time in Text form
    var textfield = new UI.TimeText({
        position: new Vector2(0, 50),
        size: new Vector2(144, 30),
        font: 'gothic-24-bold',
        text: '%M:%S, %a',
        textAlign: 'center'
    });
    wind.add(textfield);
    wind.show();
});

/*
main.on('click', 'down', function(e) {
    var card = new UI.Card();
    card.title('A Card');
    card.subtitle('Is a Window');
    card.body('The simplest window type in Pebble.js.');
    card.show();
});
*/
<commit_msg>fixing bugs<commit_after>/**
 * Welcome to Pebble.js!
 * Test
 * This is where you write your app.
 */

// Calls in required stuff
var UI = require('ui');
var Vibe = require('ui/vibe');
var Vector2 = require('vector2');

// A for loop to make array of hours
var hours = [];
for (var i = 0; i < 24; i++) {
    hours.push(i + 1);
}

// Creates a loop to make an array of hours
/*
var createItems = function() {
  var items = [];
  for (var i = 0; i < hours.length; i++) {
    items.push({
      title: hours[i]
    });
  }
  return items;
};
*/

/*
var main = new UI.Menu({
   sections:[{
     items: createItems()
  }]
});

main.show();
*/

/*
 * var main = new UI.Card({
 *   title: 'Pebble.js',
 *     icon: 'images/menu_icon.png',
 *       subtitle: 'Hello World!',
 *         body: 'Press any button.'
 *         });
 */

// Makes a menu with New Alarms
var main = new UI.Menu({
    sections: [{
        items: [{
            title: 'Add New Alarm',
            subtitle: 'New Alarm'
        }, {
            // Dr. Seuss
            title: 'One Alarm'
        }, {
            title: 'Two Alarm'
        }, {
            title: 'Red Alarm'
        }, {
            title: 'Blue Alarm'
        }]
    }]
});

main.show();

main.on('click', 'select', function(e) {
});

/*
main.on('click', 'up', function(e) {
    var menu = new UI.Menu({
        sections: [{
            items: [{
                title: 'Pebble.js',
                icon: 'images/menu_icon.png',
                subtitle: 'Can do Menus'
            }, {
                title: 'Second Item',
                subtitle: 'Subtitle Text'
            }]
        }]
    });
    menu.on('select', function(e) {
        console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex);
        console.log('The item is titled "' + e.item.title + '"');
    });
    menu.show();
});
*/

// When click middle button
main.on('click', 'up', function(e) {
    //test vibrqtion and date fubction
    console.log("foo");
    if (new Date() == new Date()) {
        Vibe.vibrate('long');
    }
    var wind = new UI.Window();
    var time = new UI.TimeText();
    // Time in Text form
    var textfield = new UI.TimeText({
        position: new Vector2(0, 50),
        size: new Vector2(144, 30),
        font: 'gothic-24-bold',
        text: '%M:%S, %a',
        textAlign: 'center'
    });
    wind.add(textfield);
    wind.show();
});

/*
main.on('click', 'down', function(e) {
    var card = new UI.Card();
    card.title('A Card');
    card.subtitle('Is a Window');
    card.body('The simplest window type in Pebble.js.');
    card.show();
});
*/
<|endoftext|>


<commit_before>var words = {
	plus : "",
	minus : "",
	multiplyBy: "",
	divideBy: ""
}

function getRelevantWords(word)
{
	var wordArray = Object.getOwnPropertyNames(words);
	var relevantWords = [];
	for(var i = 0; i < wordArray.length; i++)
	{
		if(wordArray[i].toLowerCase().substr(0, word.length) == word.toLowerCase())
		{
			relevantWords.push(wordArray[i]);
		}
	}
	return relevantWords;
}

function optionString(item)
{
	return "
" + item + "
";
}

function fullOptionString(items)
{
	var result = "";

	for(var i = 0; i < items.length; i++)
	{
		result += optionString(items[i]);
	}
	return result;
}

function autoComplete()
{
	$("#math-words").removeClass("hidden");

	var typedText = $("textarea").val();

	var relevantWord =  typedText.split(' ').pop();

	$("#math-words").html( fullOptionString( getRelevantWords(relevantWord) ));
}

$('body').on('click', '.ac-option', function() {
	var typedText = $("textarea").val();

	var resultText = typedText.substr( typedText.lastIndexOf(" "), typedText.length ) + $(this).text();

	$("textarea").val(resultText);
});
<commit_msg>Update autocomplete.js<commit_after>var words = {
	plus : "",
	minus : "",
	multiplyBy: "",
	divideBy: ""
}

function getRelevantWords(word)
{
	var wordArray = Object.getOwnPropertyNames(words);
	var relevantWords = [];
	for(var i = 0; i < wordArray.length; i++)
	{
		if(wordArray[i].toLowerCase().substr(0, word.length) == word.toLowerCase())
		{
			relevantWords.push(wordArray[i]);
		}
	}
	return relevantWords;
}

function optionString(item)
{
	return "
" + item + "
";
}

function fullOptionString(items)
{
	var result = "";

	for(var i = 0; i < items.length; i++)
	{
		result += optionString(items[i]);
	}
	return result;
}

function autoComplete()
{
	$("#math-words").removeClass("hidden");

	var typedText = $("textarea").val();

	var relevantWord =  typedText.split(' ').pop();

	$("#math-words").html( fullOptionString( getRelevantWords(relevantWord) ));
}

$('body').on('click', '.ac-option', function() {
	var typedText = $("textarea").val();

	var index = typedText.lastIndexOf(" ");

	if(index == null || index == undefined)
	{
		index = 1;
	}

	var resultText = typedText.substr(0, index) + $(this).text();

	$("textarea").val(resultText);
});
<|endoftext|>