<commit_before>import Papa from 'papaparse'
import moment from 'moment'
import store from './store'
import * as actionCreators from './actionCreators'
import {getSplits, getQuotes, processDividends} from './helpers/retrieve'
import {sortByDate, removeStrings, lowerCase, formatDate, findLastTradeDate,
	findLastOpenMarketDate, binarySearch} from './helpers/utility'
import calculateValues from './helpers/worthCalculations'

const hypoInput = document.getElementById('textInput')
const portfolioInput = document.getElementById('fileInput')
portfolioInput.addEventListener('change', handleCSV, false)

function handleCSV() {
	const file = this.files[0]
	const reader = new FileReader()
	reader.onload = () => {
		parseCSV(reader.result.trim())
	}
	reader.onerror = () => {
		throw new Error('Error reading CSV file')
	}
	reader.readAsText(file)
}

function parseCSV(csvTransactions) {
	const options = {
		header: true
	}
	const jsonTransactions = Papa.parse(csvTransactions, options)
	setUpDataMunge(jsonTransactions.data, jsonTransactions.meta.fields)
}

function setUpDataMunge(transactions, fields) {
	const processedTransactions = sortByDate(removeStrings(lowerCase(transactions, fields)))
	const hypoSymbol = hypoInput.value.toUpperCase()
	const realSymbols = Array.from(new Set(processedTransactions.map(element => element.symbol)))
	const allSymbols = realSymbols.concat([hypoSymbol])
	const firstDate = processedTransactions[0].date
	const lastDate = processedTransactions[processedTransactions.length - 1].date

	const inputData = {
		allSymbols,
		firstDate,
		lastDate
	}

	mungeData({processedTransactions, hypoSymbol, inputData})
}

function mungeData({processedTransactions, hypoSymbol, inputData}) {
	Promise.all([
		getSplits(inputData),
		getQuotes(inputData, 'd'), // daily price quotes
		getQuotes(inputData, 'v') // dividend quotes
	]).then(([processedSplits, quotes, dividends]) => {
		const processedDividends = processDividends(inputData.allSymbols, quotes, dividends)
		// Add price data
		const processedTransactionsPromises = processedTransactions.map(element => {
			const quoteMatch = binarySearch(quotes[element.symbol], formatDate(element.date))
			if(quoteMatch) {
				element.price = quoteMatch.close
				return element
			}
			return new Promise(resolve => {
				resolve(findLastTradeDate(element))
			})
		})
		Promise.all(processedTransactionsPromises).then(finalProcessedTransactions => {
			const combinedTransactions = sortByDate(finalProcessedTransactions.concat(processedDividends).concat(processedSplits))
			processTransactions(combinedTransactions, quotes, hypoSymbol)
		})
	})
}

function processTransactions(combinedTransactions, quotes, hypoSymbol) {
	const transactionsLength = combinedTransactions.length - 1
	combinedTransactions.forEach((transactionElement, index) => {
		switch(true) {
			case (transactionElement.transaction === 'buy' || transactionElement.transaction === 'sell'): {
				const quotesHypoElement = binarySearch(quotes[hypoSymbol], transactionElement.date)
				dispatchActions({
					hypoSymbol,
					quotes,
					type: transactionElement.transaction,
					date: transactionElement.date,
					real: {
						symbol: transactionElement.symbol,
						shares: transactionElement.shares,
						price: transactionElement.price
					},
					hypo: {
						shares: (transactionElement.shares * transactionElement.price) / quotesHypoElement.close,
						price: quotesHypoElement.close
					}
				})
				break
			}
			case (transactionElement.transaction === 'dividend'): {
				const currentRealSymbols = Object.keys(store.getState().realSecurities)
				const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])

				if(currentAllSymbols.includes(transactionElement.symbol)) {
					dispatchActions({
						hypoSymbol,
						quotes,
						date: transactionElement.date,
						type: transactionElement.transaction,
						symbol: transactionElement.symbol,
						dividendYield: transactionElement.dividendYield,
						price: transactionElement.price
					})
				}
			}
				break
			case (transactionElement.transaction === 'split'): {
				const currentRealSymbols = Object.keys(store.getState().realSecurities)
				const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])

				if(currentAllSymbols.includes(transactionElement.symbol)) {
					const splitPrice = binarySearch(quotes[transactionElement.symbol], transactionElement.date)
					dispatchActions({
						hypoSymbol,
						quotes,
						date: transactionElement.date,
						type: transactionElement.transaction,
						symbol: transactionElement.symbol,
						splitRatio: transactionElement.splitRatio,
						price: splitPrice.close
					})
				}
			}
				break
			default:
				throw new Error(`Invalid transaction: ${JSON.stringify(transactionElement)}`)
		}
		if(index === transactionsLength) {
			// Add most recent trade date to quotes
			const currentRealSymbols = Object.keys(store.getState().realSecurities)
			const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])
			const yesterday = moment().subtract(1, 'day')
			const lastTradeDate = findLastOpenMarketDate(yesterday)
			getQuotes({
				allSymbols: currentAllSymbols,
				firstDate: lastTradeDate,
				lastDate: lastTradeDate
			}, 'd')
				.then(data => {
					Object.keys(data).forEach(element =>
						quotes[element].push(data[element][0])
					)
					calculateValues(lastTradeDate, quotes, hypoSymbol)
				})
		}
	})
}

function dispatchActions({date, quotes, type, real, hypo, symbol, hypoSymbol, price, dividendYield, splitRatio}) {
	switch(type) {
		case 'buy':
			store.dispatch(actionCreators.buyHypoStock({
				[hypoSymbol]: {
					date,
					shares: hypo.shares,
					price: hypo.price
				}
			}))
			store.dispatch(actionCreators.buyRealStock({
				[real.symbol]: {
					date,
					shares: real.shares,
					price: real.price
				}
			}))
			break
		case 'sell':
			store.dispatch(actionCreators.sellHypoStock({
				[hypoSymbol]: {
					date,
					shares: hypo.shares,
					price: hypo.price
				}
			}))
			store.dispatch(actionCreators.sellRealStock({
				[real.symbol]: {
					date,
					shares: real.shares,
					price: real.price
				}
			}))
			break
		case 'dividend': {
			const dividendObject = {
				[symbol]: {
					date,
					price,
					dividendYield
				}
			}
			if(symbol === hypoSymbol) {
				store.dispatch(actionCreators.dividendHypoStock(dividendObject))
			}
			else {
				store.dispatch(actionCreators.dividendRealStock(dividendObject))
			}
			break
		}
		case 'split': {
			const splitObject = {
				[symbol]: {
					date,
					price,
					splitRatio
				}
			}
			if(symbol === hypoSymbol) {
				store.dispatch(actionCreators.splitHypoStock(splitObject))
			}
			else {
				store.dispatch(actionCreators.splitRealStock(splitObject))
			}
			break
		}
		default:
			throw new Error('Invalid transaction')
	}
	calculateValues(date, quotes, hypoSymbol)
}
<commit_msg>Reorder execution of Real and Hypo transactions<commit_after>import Papa from 'papaparse'
import moment from 'moment'
import store from './store'
import * as actionCreators from './actionCreators'
import {getSplits, getQuotes, processDividends} from './helpers/retrieve'
import {sortByDate, removeStrings, lowerCase, formatDate, findLastTradeDate,
	findLastOpenMarketDate, binarySearch} from './helpers/utility'
import calculateValues from './helpers/worthCalculations'

const hypoInput = document.getElementById('textInput')
const portfolioInput = document.getElementById('fileInput')
portfolioInput.addEventListener('change', handleCSV, false)

function handleCSV() {
	const file = this.files[0]
	const reader = new FileReader()
	reader.onload = () => {
		parseCSV(reader.result.trim())
	}
	reader.onerror = () => {
		throw new Error('Error reading CSV file')
	}
	reader.readAsText(file)
}

function parseCSV(csvTransactions) {
	const options = {
		header: true
	}
	const jsonTransactions = Papa.parse(csvTransactions, options)
	setUpDataMunge(jsonTransactions.data, jsonTransactions.meta.fields)
}

function setUpDataMunge(transactions, fields) {
	const processedTransactions = sortByDate(removeStrings(lowerCase(transactions, fields)))
	const hypoSymbol = hypoInput.value.toUpperCase()
	const realSymbols = Array.from(new Set(processedTransactions.map(element => element.symbol)))
	const allSymbols = realSymbols.concat([hypoSymbol])
	const firstDate = processedTransactions[0].date
	const lastDate = processedTransactions[processedTransactions.length - 1].date

	const inputData = {
		allSymbols,
		firstDate,
		lastDate
	}

	mungeData({processedTransactions, hypoSymbol, inputData})
}

function mungeData({processedTransactions, hypoSymbol, inputData}) {
	Promise.all([
		getSplits(inputData),
		getQuotes(inputData, 'd'), // daily price quotes
		getQuotes(inputData, 'v') // dividend quotes
	]).then(([processedSplits, quotes, dividends]) => {
		const processedDividends = processDividends(inputData.allSymbols, quotes, dividends)
		// Add price data
		const processedTransactionsPromises = processedTransactions.map(element => {
			const quoteMatch = binarySearch(quotes[element.symbol], formatDate(element.date))
			if(quoteMatch) {
				element.price = quoteMatch.close
				return element
			}
			return new Promise(resolve => {
				resolve(findLastTradeDate(element))
			})
		})
		Promise.all(processedTransactionsPromises).then(finalProcessedTransactions => {
			const combinedTransactions = sortByDate(finalProcessedTransactions.concat(processedDividends).concat(processedSplits))
			processTransactions(combinedTransactions, quotes, hypoSymbol)
		})
	})
}

function processTransactions(combinedTransactions, quotes, hypoSymbol) {
	const transactionsLength = combinedTransactions.length - 1
	combinedTransactions.forEach((transactionElement, index) => {
		switch(true) {
			case (transactionElement.transaction === 'buy' || transactionElement.transaction === 'sell'): {
				const quotesHypoElement = binarySearch(quotes[hypoSymbol], transactionElement.date)
				dispatchActions({
					hypoSymbol,
					quotes,
					type: transactionElement.transaction,
					date: transactionElement.date,
					real: {
						symbol: transactionElement.symbol,
						shares: transactionElement.shares,
						price: transactionElement.price
					},
					hypo: {
						shares: (transactionElement.shares * transactionElement.price) / quotesHypoElement.close,
						price: quotesHypoElement.close
					}
				})
				break
			}
			case (transactionElement.transaction === 'dividend'): {
				const currentRealSymbols = Object.keys(store.getState().realSecurities)
				const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])

				if(currentAllSymbols.includes(transactionElement.symbol)) {
					dispatchActions({
						hypoSymbol,
						quotes,
						date: transactionElement.date,
						type: transactionElement.transaction,
						symbol: transactionElement.symbol,
						dividendYield: transactionElement.dividendYield,
						price: transactionElement.price
					})
				}
			}
				break
			case (transactionElement.transaction === 'split'): {
				const currentRealSymbols = Object.keys(store.getState().realSecurities)
				const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])

				if(currentAllSymbols.includes(transactionElement.symbol)) {
					const splitPrice = binarySearch(quotes[transactionElement.symbol], transactionElement.date)
					dispatchActions({
						hypoSymbol,
						quotes,
						date: transactionElement.date,
						type: transactionElement.transaction,
						symbol: transactionElement.symbol,
						splitRatio: transactionElement.splitRatio,
						price: splitPrice.close
					})
				}
			}
				break
			default:
				throw new Error(`Invalid transaction: ${JSON.stringify(transactionElement)}`)
		}
		if(index === transactionsLength) {
			// Add most recent trade date to quotes
			const currentRealSymbols = Object.keys(store.getState().realSecurities)
			const currentAllSymbols = currentRealSymbols.concat([hypoSymbol])
			const yesterday = moment().subtract(1, 'day')
			const lastTradeDate = findLastOpenMarketDate(yesterday)
			getQuotes({
				allSymbols: currentAllSymbols,
				firstDate: lastTradeDate,
				lastDate: lastTradeDate
			}, 'd')
				.then(data => {
					Object.keys(data).forEach(element =>
						quotes[element].push(data[element][0])
					)
					calculateValues(lastTradeDate, quotes, hypoSymbol)
				})
		}
	})
}

function dispatchActions({date, quotes, type, real, hypo, symbol, hypoSymbol, price, dividendYield, splitRatio}) {
	switch(type) {
		case 'buy':
			store.dispatch(actionCreators.buyRealStock({
				[real.symbol]: {
					date,
					shares: real.shares,
					price: real.price
				}
			}))
			store.dispatch(actionCreators.buyHypoStock({
				[hypoSymbol]: {
					date,
					shares: hypo.shares,
					price: hypo.price
				}
			}))
			break
		case 'sell':
			store.dispatch(actionCreators.sellRealStock({
				[real.symbol]: {
					date,
					shares: real.shares,
					price: real.price
				}
			}))
			store.dispatch(actionCreators.sellHypoStock({
				[hypoSymbol]: {
					date,
					shares: hypo.shares,
					price: hypo.price
				}
			}))
			break
		case 'dividend': {
			const dividendObject = {
				[symbol]: {
					date,
					price,
					dividendYield
				}
			}
			if(symbol !== hypoSymbol) {
				store.dispatch(actionCreators.dividendRealStock(dividendObject))
			}
			else {
				store.dispatch(actionCreators.dividendHypoStock(dividendObject))
			}
			break
		}
		case 'split': {
			const splitObject = {
				[symbol]: {
					date,
					price,
					splitRatio
				}
			}
			if(symbol !== hypoSymbol) {
				store.dispatch(actionCreators.splitRealStock(splitObject))
			}
			else {
				store.dispatch(actionCreators.splitHypoStock(splitObject))
			}
			break
		}
		default:
			throw new Error('Invalid transaction')
	}
	calculateValues(date, quotes, hypoSymbol)
}
<|endoftext|>


<commit_before>'use strict';

/**
 * plugin.js
 *
 * Released under LGPL License.
 * Copyright (c) 2015 SIRAP SAS All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * plugin.js Tinymce plugin paginate
 * @file plugin.js
 * @module
 * @name tinycmce-plugin-paginate
 * @description Plugin for tinymce wysiwyg HTML editor that provide pagination in the editor.
 * @link https://github.com/sirap-group/tinymce-plugin-paginate
 * @author Rémi Becheras
 * @author Groupe SIRAP
 * @license GNU GPL-v2 http://www.tinymce.com/license
 * @listens tinymce.editor~event:init
 * @listens tinymce.editor~event:change
 * @listens tinymce.editor~event:SetContent
 * @listens tinymce.editor~event:NodeChange
 * @listens tinymce.editor.document~event:PageChange
 * @version 1.0.0
 */

/**
 * Tinymce library - injected by the plugin loader.
 * @external tinymce
 * @see {@link https://www.tinymce.com/docs/api/class/tinymce/|Tinymce API Reference}
 */
/*global tinymce:true */

/**
 * The jQuery plugin namespace - plugin dependency.
 * @external "jQuery.fn"
 * @see {@link http://learn.jquery.com/plugins/|jQuery Plugins}
 */
/*global jquery:true */

/**
 * Paginator class
 * @type {Paginator}
 * @global
 */
var Paginator = require('./classes/Paginator');

/**
 * Paginator ui module
 * @type {object}
 * @global
 */
var ui = require('./utils/ui');

/**
 * InvalidPageHeightError
 * @type {InvalidPageHeightError}
 * @global
 */
var InvalidPageHeightError = Paginator.errors.InvalidPageHeightError;

/**
 * Tinymce plugin paginate
 * @function
 * @global
 * @param {tinymce.Editor} editor - The injected tinymce editor.
 * @returns void
 */
function tinymcePluginPaginate(editor) {

  /**
   * Debug all useful editor events to see the order of their happen
   * @function
   * @private
   */
  function _debugEditorEvents(){
    var myevents = [];
    var mycount = {
      init: 0,
      change: 0,
      nodechange: 0,
      setcontent: 0
    };

    editor.on('init',function(evt){
      console.log(editor);
      myevents.push({'init':evt});
      mycount.init ++;
      console.log(myevents,mycount);
      // alert('pause after "init" event');
    });
    editor.on('change',function(evt){
      myevents.push({'change':evt});
      mycount.change ++;
      console.log(myevents,mycount);
      // alert('pause after "change" event');
    });
    editor.on('NodeChange',function(evt){
      myevents.push({'NodeChange':evt});
      mycount.nodechange ++;
      console.log(myevents,mycount);
      // alert('pause after "NodeChange" event');
    });
    editor.on('SetContent',function(evt){
      myevents.push({'SetContent':evt});
      mycount.setcontent ++;
      console.log(myevents,mycount);
      // alert('pause after "SetContent" event');
    });

    window.logEvents = myevents;
    window.logCount = mycount;
  }

  /**
   * On 'PageChange' event listener. Update page rank input on paginator's navigation buttons.
   * @function
   * @private
   */
  function onPageChange(evt){
    ui.updatePageRankInput(evt.toPage.rank);
  }

  function onRemoveEditor(evt){
    ui.removeNavigationButtons();
    paginator.destroy();
    watchPageIterationsCount = 0;
    paginatorListens = false;
  }

  /**
   * Wrap Paginator#watchPage() in try catch statements and private function to allow watch recursively on error
   * @function
   * @private
   * @returns void
   * @throws {Error} if error thrown is not instance of InvalidPageHeightError
   */
  function watchPage(){
    try {
      paginator.watchPage();
    } catch (e) {
      watchPageIterationsCount++;
      // Due to a suspecte bug in tinymce that break the binding of DOM elements with the paginator.
      if (e instanceof InvalidPageHeightError) {
        console.error(e.message+'... re-init paginator then watch page again...');
        paginator.init();
        if (watchPageIterationsCount<10) {
          watchPage();
        } else {
          watchPageIterationsCount = 0;
        }
      } else throw e;
    }
  }

  /**
  * A 'Paginator' object to handle all paginating behaviors.
  * @var {Paginator} paginator
  * @global
  */
  var paginator;

  /**
   * A 'Display' object to handle graphics behaviors for the paginator needs.
   * @var {Display} display
   * @global
   */
  var display;

  /**
   * Is set to true when paginator is initialized.
   * @var {Boolean} paginatorListens
   * @global
   */
  var paginatorListens = false;

  /**
   * Count of the iterations of watchPage() calls triggered by thrown of `InvalidPageHeightError`. This is a temporary bugfix
   * @var {integer}
   * @global
   */
  var watchPageIterationsCount=0;

  // _debugEditorEvents();

  editor.once('init',function(){
    paginator = new Paginator('A4','portrait', editor);
    if(!paginatorListens) paginator.init();
    paginatorListens = true;
    ui.appendNavigationButtons(paginator);
    editor.dom.bind(editor.getDoc(),'PageChange',onPageChange);
  });

  editor.on('remove',onRemoveEditor);

  editor.once('change',function(evt){
    paginatorListens = !!paginator;
    if(paginatorListens) paginator.init();
  });
  editor.on('change',function(){
    if(paginatorListens) paginator.watchPage();
  });
  editor.on('SetContent',function(){
    //if(paginatorStartListening) paginator.init();
  });
  editor.on('NodeChange',function(){
    if (paginatorListens) {
      try {
        paginator.gotoFocusedPage();
      } catch (e) {
        console.info('Can\'t go to focused page now.');
        console.error(e.stack);
      }
    }
  });

}

// Add the plugin to the tinymce PluginManager
tinymce.PluginManager.add('paginate', tinymcePluginPaginate);
<commit_msg>clean code<commit_after>'use strict';

/**
 * plugin.js
 *
 * Released under LGPL License.
 * Copyright (c) 2015 SIRAP SAS All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * plugin.js Tinymce plugin paginate
 * @file plugin.js
 * @module
 * @name tinycmce-plugin-paginate
 * @description Plugin for tinymce wysiwyg HTML editor that provide pagination in the editor.
 * @link https://github.com/sirap-group/tinymce-plugin-paginate
 * @author Rémi Becheras
 * @author Groupe SIRAP
 * @license GNU GPL-v2 http://www.tinymce.com/license
 * @listens tinymce.editor~event:init
 * @listens tinymce.editor~event:change
 * @listens tinymce.editor~event:SetContent
 * @listens tinymce.editor~event:NodeChange
 * @listens tinymce.editor.document~event:PageChange
 * @version 1.0.0
 */

/**
 * Tinymce library - injected by the plugin loader.
 * @external tinymce
 * @see {@link https://www.tinymce.com/docs/api/class/tinymce/|Tinymce API Reference}
 */
/*global tinymce:true */

/**
 * The jQuery plugin namespace - plugin dependency.
 * @external "jQuery.fn"
 * @see {@link http://learn.jquery.com/plugins/|jQuery Plugins}
 */
/*global jquery:true */

/**
 * Paginator class
 * @type {Paginator}
 * @global
 */
var Paginator = require('./classes/Paginator');

/**
 * Paginator ui module
 * @type {object}
 * @global
 */
var ui = require('./utils/ui');

/**
 * InvalidPageHeightError
 * @type {InvalidPageHeightError}
 * @global
 */
var InvalidPageHeightError = Paginator.errors.InvalidPageHeightError;

/**
 * Tinymce plugin paginate
 * @function
 * @global
 * @param {tinymce.Editor} editor - The injected tinymce editor.
 * @returns void
 */
function tinymcePluginPaginate(editor) {

  /**
   * Debug all useful editor events to see the order of their happen
   * @function
   * @private
   */
  function _debugEditorEvents(){
    var myevents = [];
    var mycount = {
      init: 0,
      change: 0,
      nodechange: 0,
      setcontent: 0
    };

    editor.on('init',function(evt){
      console.log(editor);
      myevents.push({'init':evt});
      mycount.init ++;
      console.log(myevents,mycount);
      // alert('pause after "init" event');
    });
    editor.on('change',function(evt){
      myevents.push({'change':evt});
      mycount.change ++;
      console.log(myevents,mycount);
      // alert('pause after "change" event');
    });
    editor.on('NodeChange',function(evt){
      myevents.push({'NodeChange':evt});
      mycount.nodechange ++;
      console.log(myevents,mycount);
      // alert('pause after "NodeChange" event');
    });
    editor.on('SetContent',function(evt){
      myevents.push({'SetContent':evt});
      mycount.setcontent ++;
      console.log(myevents,mycount);
      // alert('pause after "SetContent" event');
    });

    window.logEvents = myevents;
    window.logCount = mycount;
  }

  /**
   * On 'PageChange' event listener. Update page rank input on paginator's navigation buttons.
   * @function
   * @private
   */
  function onPageChange(evt){
    ui.updatePageRankInput(evt.toPage.rank);
  }

  function onRemoveEditor(evt){
    ui.removeNavigationButtons();
    paginator.destroy();
    watchPageIterationsCount = 0;
    paginatorListens = false;
  }

  /**
   * Wrap Paginator#watchPage() in try catch statements and private function to allow watch recursively on error
   * @function
   * @private
   * @returns void
   * @throws {Error} if error thrown is not instance of InvalidPageHeightError
   */
  function watchPage(){
    try {
      paginator.watchPage();
    } catch (e) {
      watchPageIterationsCount++;
      // Due to a suspecte bug in tinymce that break the binding of DOM elements with the paginator.
      if (e instanceof InvalidPageHeightError) {
        console.error(e.message+'... re-init paginator then watch page again...');
        paginator.init();
        if (watchPageIterationsCount<10) {
          watchPage();
        } else {
          watchPageIterationsCount = 0;
        }
      } else throw e;
    }
  }

  /**
  * A 'Paginator' object to handle all paginating behaviors.
  * @var {Paginator} paginator
  * @global
  */
  var paginator;

  /**
   * A 'Display' object to handle graphics behaviors for the paginator needs.
   * @var {Display} display
   * @global
   */
  var display;

  /**
   * Is set to true when paginator is initialized.
   * @var {Boolean} paginatorListens
   * @global
   */
  var paginatorListens = false;

  /**
   * Count of the iterations of watchPage() calls triggered by thrown of `InvalidPageHeightError`. This is a temporary bugfix
   * @var {integer}
   * @global
   */
  var watchPageIterationsCount=0;

  // _debugEditorEvents();

  editor.once('init',function(){
    paginator = new Paginator('A4','portrait', editor);
    if(!paginatorListens) paginator.init();
    paginatorListens = true;
    ui.appendNavigationButtons(paginator);
    editor.dom.bind(editor.getDoc(),'PageChange',onPageChange);
  });

  editor.on('remove',onRemoveEditor);

  editor.once('change',function(evt){
    paginatorListens = !!paginator;
    if(paginatorListens) paginator.init();
  });

  editor.on('change',function(){
    if(paginatorListens) paginator.watchPage();
  });

  editor.on('SetContent',function(){
    //if(paginatorStartListening) paginator.init();
  });

  editor.on('NodeChange',function(){
    if (paginatorListens) {
      try {
        paginator.gotoFocusedPage();
      } catch (e) {
        console.info('Can\'t go to focused page now.');
        console.error(e.stack);
      }
    }
  });

}

// Add the plugin to the tinymce PluginManager
tinymce.PluginManager.add('paginate', tinymcePluginPaginate);
<|endoftext|>


<commit_before>/**
 * FCUI (Fengchao UI)
 * Copyright 2014 Baidu Inc. All rights reserved.
 *
 * @file
 * @author Han Bing Feng (hanbingfeng@baidu.com)
 * @param {Function} require require
 * @return {Object} main
 */
define(function (require) {
    var u = require('underscore');
    var main = require('esui/main');
    main.esuiVersion = main.version;

    /**
     * 绑定全局扩展
     *
     * 通过此方法绑定的扩展，会对所有的控件实例生效
     *
     * 每一次全局扩展生成实例时，均会复制`options`对象，而不会直接使用引用
     *
     * 派生自ESUI 3.1.0-beta.3
     * @param {string} type 扩展类型
     * @param {Object} option 扩展初始化参数
     */
    main.attachExtension = function (type, option) {
        if (typeof this.globalExtensionOptions === 'undefined') {
            this.globalExtensionOptions = {};
        }
        this.globalExtensionOptions[type] = option || {};
    };

    /**
     * 创建全局扩展对象
     * ESUI里的实现有误，修正过来
     *
     * @return {Extension[]}
     */
    main.createGlobalExtensions = function () {
        var options = this.globalExtensionOptions;
        var extensions = [];
        u.each(options, function (option, type) {
            var extension = this.createExtension(type, option);
            extension && extensions.push(extension);
        }, this);

        return extensions;
    };

    /**
     * 控件库配置数据
     *
     * @type {Object}
     * @ignore
     */
    var config = {
        uiPrefix: 'data-ui',
        extensionPrefix: 'data-ui-extension',
        customElementPrefix: 'esui',
        instanceAttr: 'data-ctrl-id',
        viewContextAttr: 'data-ctrl-view-context',
        uiClassPrefix: 'ui',
        skinClassPrefix: 'skin',
        stateClassPrefix: 'state'
    };

    /**
     * 从容器DOM元素批量初始化内部的控件渲染
     * 派生自ESUI 3.1.0-beta.3。
     * 做了如下改动
     * 1. options里加了group参数。指定的话，本次init的所有元素将放入指定的
     *    group中。
     * @param {HTMLElement} [wrap=document.body] 容器DOM元素，默认
     * @param {Object} [options] init参数
     * @param {Object} [options.viewContext] 视图环境
     * @param {Object} [options.properties] 属性集合，通过id映射
     * @param {Object} [options.valueReplacer] 属性值替换函数
     * @param {Object} [options.parent] 初始化的元素共有的父元素
     * @param {Object} [options.group] 初始化的元素共有的组
     * @return {Control[]} 初始化的控件对象集合
     */
    /* eslint-disable fecs-max-statements */
    /* 从ESUI拿的旧的main，暂时去掉50行的限制 */
    main.init = function (wrap, options) {
        wrap = wrap || document.body;
        options = options || {};

        var valueReplacer = options.valueReplacer || function (value) {
            return value;
        };

        /**
         * 将字符串数组join成驼峰形式
         *
         * @param {string[]} source 源字符串数组
         * @return {string}
         * @ignore
         */
        function joinCamelCase(source) {
            function replacer(c) {
                return c.toUpperCase();
            }

            for (var i = 1, len = source.length; i < len; i++) {
                source[i] = source[i].replace(/^[a-z]/, replacer);
            }

            return source.join('');
        }

        /**
         * 不覆盖目标对象成员的extend
         *
         * @param {Object} target 目标对象
         * @param {Object} source 源对象
         * @ignore
         */
        function noOverrideExtend(target, source) {
            for (var key in source) {
                if (!(key in target)) {
                    target[key] = source[key];
                }
            }
        }

        /**
         * 将标签解析的值附加到option对象上
         *
         * @param {Object} optionObject option对象
         * @param {string[]} terms 经过切分的标签名解析结果
         * @param {string} value 属性值
         * @ignore
         */
        function extendToOption(optionObject, terms, value) {
            if (terms.length === 0) {
                noOverrideExtend(
                    optionObject,
                    main.parseAttribute(value, valueReplacer)
                );
            }
            else {
                optionObject[joinCamelCase(terms)] = valueReplacer(value);
            }
        }

        function matchReplace(match, ch) {
            return ch.toUpperCase();
        }

        // 把dom元素存储到临时数组中
        // 控件渲染的过程会导致Collection的改变
        var rawElements = wrap.getElementsByTagName('*');
        var elements = [];
        var i;
        var len;
        for (i = 0, len = rawElements.length; i < len; i++) {
            if (rawElements[i].nodeType === 1) {
                elements.push(rawElements[i]);
            }
        }

        var uiPrefix = main.getConfig('uiPrefix');
        var extPrefix = main.getConfig('extensionPrefix');
        var customElementPrefix = main.getConfig('customElementPrefix');
        var uiPrefixLen = uiPrefix.length;
        var extPrefixLen = extPrefix.length;
        var properties = options.properties || {};
        var controls = [];
        for (i = 0, len = elements.length; i < len; i++) {
            var element = elements[i];

            // 有时候，一个控件会自己把`main.innerHTML`生成子控件，比如`Panel`，
            // 但这边有缓存这些子元素，可能又会再生成一次，所以要去掉
            if (element.getAttribute(config.instanceAttr)) {
                continue;
            }

            var attributes = element.attributes;
            var controlOptions = {};
            var extensionOptions = {};
            var terms;
            var extOption;

            // 解析attribute中的参数
            for (var j = 0, attrLen = attributes.length; j < attrLen; j++) {
                var attribute = attributes[j];
                var name = attribute.name;
                var value = attribute.value;

                if (name.indexOf(extPrefix) === 0) {
                    // 解析extension的key
                    terms = name.slice(extPrefixLen + 1).split('-');
                    var extKey = terms[0];
                    terms.shift();

                    // 初始化该key的option对象
                    extOption = extensionOptions[extKey];
                    if (!extOption) {
                        extOption = extensionOptions[extKey] = {};
                    }

                    extendToOption(extOption, terms, value);
                }
                else if (name.indexOf(uiPrefix) === 0) {
                    terms = name.length === uiPrefixLen
                        ? []
                        : name.slice(uiPrefixLen + 1).split('-');
                    extendToOption(controlOptions, terms, value);
                }
            }

            // 根据选项创建控件
            var type = controlOptions.type;
            if (!type) {
                var nodeName = element.nodeName.toLowerCase();
                var esuiPrefixIndex = nodeName.indexOf(customElementPrefix);
                if (esuiPrefixIndex === 0) {
                    var typeFromCustomElement = nodeName.replace(
                        /-(\S)/g,
                        matchReplace
                    );
                    typeFromCustomElement = typeFromCustomElement.slice(
                        customElementPrefix.length
                    );
                    controlOptions.type = typeFromCustomElement;
                    type = typeFromCustomElement;
                }
            }
            if (type) {
                // 从用户传入的properties中merge控件初始化属性选项
                var controlId = controlOptions.id;
                var customOptions = controlId
                    ? properties[controlId]
                    : {};
                var key;

                for (key in customOptions) {
                    if (customOptions.hasOwnProperty(key)) {
                        controlOptions[key] = valueReplacer(customOptions[key]);
                    }
                }

                // 创建控件的插件
                var extensions = controlOptions.extensions || [];
                controlOptions.extensions = extensions;
                for (key in extensionOptions) {
                    if (extensionOptions.hasOwnProperty(key)) {
                        extOption = extensionOptions[key];
                        var extension = main.createExtension(
                            extOption.type,
                            extOption
                        );
                        extension && extensions.push(extension);
                    }
                }

                // 绑定视图环境和控件主元素
                controlOptions.viewContext = options.viewContext;
                // 容器类控件会需要渲染自己的`innerHTML`，
                // 这种渲染使用`initChildren`，再调用`main.init`，
                // 因此需要把此处`main.init`的参数交给控件，方便再带回来，
                // 以便`properties`、`valueReplacer`之类的能保留
                controlOptions.renderOptions = options;
                controlOptions.main = element;
                if (typeof options.group !== 'undefined') {
                    controlOptions.group = options.group;
                }

                // 创建控件
                var control = main.create(type, controlOptions);
                if (control) {
                    controls.push(control);
                    if (options.parent) {
                        options.parent.addChild(control);
                    }
                    try {
                        control.render();
                    }
                    catch (ex) {
                        var error = new Error(
                            'Render control '
                                + '"' + (control.id || 'anonymous') + '" '
                                + 'of type ' + control.type + ' '
                                + 'failed because: '
                                + ex.message
                        );
                        error.actualError = ex;
                        throw error;
                    }
                }
            }
        }

        return controls;
    };
    /* eslint-enable fecs-max-statements */

    // 导入register相关的代码，因为要做alias
    //

    /**
     * 控件类容器
     *
     * @type {Object}
     * @ignore
     */
    var controlClasses = {};

    /**
     * 注册控件类
     *
     * 该方法通过类的`prototype.type`识别控件类型信息。
     *
     * @param {Function} controlClass 控件类
     * @throws
     * 已经有相同`prototype.type`的控件类存在，不能重复注册同类型控件
     */
    main.register = function (controlClass) {
        registerClass(controlClass, controlClasses);
    };

    /**
     * 用别名注册一个控件
     * @param {string} aliasName 别名
     * @param {Control} control 控件类
     * @return {Control} 直接返回control
     */
    main.alias = function (aliasName, control) {
        registerAlias(aliasName, control, controlClasses);
        return control;
    };

    /**
     * 给控件一个别名。可以根据别名找到控件，但控件本身的type仍不变。
     *
     * @param {string} aliasName 控件别名
     * @param {Function} classFunc 控件类
     * @param {Object} container 类容器
     */
    function registerAlias(aliasName, classFunc, container) {
        if (typeof classFunc === 'function') {
            if (aliasName in container) {
                throw new Error(aliasName + ' is exists!');
            }

            container[aliasName] = classFunc;
        }
    }

   /**
     * 注册类。用于控件类、规则类或扩展类注册
     *
     * @param {Function} classFunc 类Function
     * @param {Object} container 类容器
     * @ignore
     */
    function registerClass(classFunc, container) {
        if (typeof classFunc === 'function') {
            var type = classFunc.prototype.type;
            if (type in container) {
                throw new Error(type + ' is exists!');
            }

            container[type] = classFunc;
        }
    }

    /**
     * 扩展类容器
     *
     * @type {Object}
     * @ignore
     */
    var extensionClasses = {};

    /**
     * 注册扩展类。
     *
     * 该方法通过类的`prototype.type`识别扩展类型信息
     *
     * @param {Function} extensionClass 扩展类
     * @throws
     * 已经有相同`prototype.type`的扩展类存在，不能重复注册同类型扩展
     */
    main.registerExtension = function (extensionClass) {
        registerClass(extensionClass, extensionClasses);
    };

    /**
     * 创建扩展
     *
     * @param {string} type 扩展类型
     * @param {Object} options 初始化参数
     * @return {Extension}
     */
    main.createExtension = function (type, options) {
        return createInstance(type, options, extensionClasses);
    };

    /**
     * 验证规则类容器
     *
     * @type {Object}
     * @ignore
     */
    var ruleClasses = [];

    /**
     * 注册控件验证规则类
     *
     * 该方法通过类的`prototype.type`识别验证规则类型信息
     *
     * @param {Function} ruleClass 验证规则类
     * @param {number} priority 优先级，越小的优先级越高
     * @throws
     * 已经有相同`prototype.type`的验证规则类存在，不能重复注册同类型验证规则
     */
    main.registerRule = function (ruleClass, priority) {
        // 多个Rule共享一个属性似乎也没问题
        ruleClasses.push({type: ruleClass, priority: priority});
        // 能有几个规则，这里就不优化为插入排序了
        ruleClasses.sort(
            function (x, y) {
                return x.priority - y.priority;
            }
        );
    };

    /**
     * 创建控件实例需要的验证规则
     *
     * @param {Control} control 控件实例
     * @return {validator.Rule[]} 验证规则数组
     */
    main.createRulesByControl = function (control) {
        var rules = [];
        for (var i = 0; i < ruleClasses.length; i++) {
            var RuleClass = ruleClasses[i].type;
            if (control.get(RuleClass.prototype.type) != null) {
                rules.push(new RuleClass());
            }
        }

        return rules;
    };

    /**
     * 创建类实例。用于控件类、规则类或扩展类的实例创建
     *
     * @param {string} type 类型
     * @param {Object} options 初始化参数
     * @param {Object} container 类容器
     * @ignore
     * @return {Control} 创建的控件
     */
    function createInstance(type, options, container) {
        var Constructor = container[type];
        if (Constructor) {
            delete options.type;
            return new Constructor(options);
        }

        return null;
    }

    /**
     * 创建控件
     *
     * @param {string} type 控件类型
     * @param {Object} options 初始化参数
     * @return {Control}
     */
    main.create = function (type, options) {
        return createInstance(type, options, controlClasses);
    };

    main.version = '0.0.2-alpha.33';

    return main;
});
<commit_msg>create with ESUI fallback<commit_after>/**
 * FCUI (Fengchao UI)
 * Copyright 2014 Baidu Inc. All rights reserved.
 *
 * @file
 * @author Han Bing Feng (hanbingfeng@baidu.com)
 * @param {Function} require require
 * @return {Object} main
 */
define(function (require) {
    var u = require('underscore');
    var main = require('esui/main');
    main.esuiVersion = main.version;

    /**
     * 绑定全局扩展
     *
     * 通过此方法绑定的扩展，会对所有的控件实例生效
     *
     * 每一次全局扩展生成实例时，均会复制`options`对象，而不会直接使用引用
     *
     * 派生自ESUI 3.1.0-beta.3
     * @param {string} type 扩展类型
     * @param {Object} option 扩展初始化参数
     */
    main.attachExtension = function (type, option) {
        if (typeof this.globalExtensionOptions === 'undefined') {
            this.globalExtensionOptions = {};
        }
        this.globalExtensionOptions[type] = option || {};
    };

    /**
     * 创建全局扩展对象
     * ESUI里的实现有误，修正过来
     *
     * @return {Extension[]}
     */
    main.createGlobalExtensions = function () {
        var options = this.globalExtensionOptions;
        var extensions = [];
        u.each(options, function (option, type) {
            var extension = this.createExtension(type, option);
            extension && extensions.push(extension);
        }, this);

        return extensions;
    };

    /**
     * 控件库配置数据
     *
     * @type {Object}
     * @ignore
     */
    var config = {
        uiPrefix: 'data-ui',
        extensionPrefix: 'data-ui-extension',
        customElementPrefix: 'esui',
        instanceAttr: 'data-ctrl-id',
        viewContextAttr: 'data-ctrl-view-context',
        uiClassPrefix: 'ui',
        skinClassPrefix: 'skin',
        stateClassPrefix: 'state'
    };

    /**
     * 从容器DOM元素批量初始化内部的控件渲染
     * 派生自ESUI 3.1.0-beta.3。
     * 做了如下改动
     * 1. options里加了group参数。指定的话，本次init的所有元素将放入指定的
     *    group中。
     * @param {HTMLElement} [wrap=document.body] 容器DOM元素，默认
     * @param {Object} [options] init参数
     * @param {Object} [options.viewContext] 视图环境
     * @param {Object} [options.properties] 属性集合，通过id映射
     * @param {Object} [options.valueReplacer] 属性值替换函数
     * @param {Object} [options.parent] 初始化的元素共有的父元素
     * @param {Object} [options.group] 初始化的元素共有的组
     * @return {Control[]} 初始化的控件对象集合
     */
    /* eslint-disable fecs-max-statements */
    /* 从ESUI拿的旧的main，暂时去掉50行的限制 */
    main.init = function (wrap, options) {
        wrap = wrap || document.body;
        options = options || {};

        var valueReplacer = options.valueReplacer || function (value) {
            return value;
        };

        /**
         * 将字符串数组join成驼峰形式
         *
         * @param {string[]} source 源字符串数组
         * @return {string}
         * @ignore
         */
        function joinCamelCase(source) {
            function replacer(c) {
                return c.toUpperCase();
            }

            for (var i = 1, len = source.length; i < len; i++) {
                source[i] = source[i].replace(/^[a-z]/, replacer);
            }

            return source.join('');
        }

        /**
         * 不覆盖目标对象成员的extend
         *
         * @param {Object} target 目标对象
         * @param {Object} source 源对象
         * @ignore
         */
        function noOverrideExtend(target, source) {
            for (var key in source) {
                if (!(key in target)) {
                    target[key] = source[key];
                }
            }
        }

        /**
         * 将标签解析的值附加到option对象上
         *
         * @param {Object} optionObject option对象
         * @param {string[]} terms 经过切分的标签名解析结果
         * @param {string} value 属性值
         * @ignore
         */
        function extendToOption(optionObject, terms, value) {
            if (terms.length === 0) {
                noOverrideExtend(
                    optionObject,
                    main.parseAttribute(value, valueReplacer)
                );
            }
            else {
                optionObject[joinCamelCase(terms)] = valueReplacer(value);
            }
        }

        function matchReplace(match, ch) {
            return ch.toUpperCase();
        }

        // 把dom元素存储到临时数组中
        // 控件渲染的过程会导致Collection的改变
        var rawElements = wrap.getElementsByTagName('*');
        var elements = [];
        var i;
        var len;
        for (i = 0, len = rawElements.length; i < len; i++) {
            if (rawElements[i].nodeType === 1) {
                elements.push(rawElements[i]);
            }
        }

        var uiPrefix = main.getConfig('uiPrefix');
        var extPrefix = main.getConfig('extensionPrefix');
        var customElementPrefix = main.getConfig('customElementPrefix');
        var uiPrefixLen = uiPrefix.length;
        var extPrefixLen = extPrefix.length;
        var properties = options.properties || {};
        var controls = [];
        for (i = 0, len = elements.length; i < len; i++) {
            var element = elements[i];

            // 有时候，一个控件会自己把`main.innerHTML`生成子控件，比如`Panel`，
            // 但这边有缓存这些子元素，可能又会再生成一次，所以要去掉
            if (element.getAttribute(config.instanceAttr)) {
                continue;
            }

            var attributes = element.attributes;
            var controlOptions = {};
            var extensionOptions = {};
            var terms;
            var extOption;

            // 解析attribute中的参数
            for (var j = 0, attrLen = attributes.length; j < attrLen; j++) {
                var attribute = attributes[j];
                var name = attribute.name;
                var value = attribute.value;

                if (name.indexOf(extPrefix) === 0) {
                    // 解析extension的key
                    terms = name.slice(extPrefixLen + 1).split('-');
                    var extKey = terms[0];
                    terms.shift();

                    // 初始化该key的option对象
                    extOption = extensionOptions[extKey];
                    if (!extOption) {
                        extOption = extensionOptions[extKey] = {};
                    }

                    extendToOption(extOption, terms, value);
                }
                else if (name.indexOf(uiPrefix) === 0) {
                    terms = name.length === uiPrefixLen
                        ? []
                        : name.slice(uiPrefixLen + 1).split('-');
                    extendToOption(controlOptions, terms, value);
                }
            }

            // 根据选项创建控件
            var type = controlOptions.type;
            if (!type) {
                var nodeName = element.nodeName.toLowerCase();
                var esuiPrefixIndex = nodeName.indexOf(customElementPrefix);
                if (esuiPrefixIndex === 0) {
                    var typeFromCustomElement = nodeName.replace(
                        /-(\S)/g,
                        matchReplace
                    );
                    typeFromCustomElement = typeFromCustomElement.slice(
                        customElementPrefix.length
                    );
                    controlOptions.type = typeFromCustomElement;
                    type = typeFromCustomElement;
                }
            }
            if (type) {
                // 从用户传入的properties中merge控件初始化属性选项
                var controlId = controlOptions.id;
                var customOptions = controlId
                    ? properties[controlId]
                    : {};
                var key;

                for (key in customOptions) {
                    if (customOptions.hasOwnProperty(key)) {
                        controlOptions[key] = valueReplacer(customOptions[key]);
                    }
                }

                // 创建控件的插件
                var extensions = controlOptions.extensions || [];
                controlOptions.extensions = extensions;
                for (key in extensionOptions) {
                    if (extensionOptions.hasOwnProperty(key)) {
                        extOption = extensionOptions[key];
                        var extension = main.createExtension(
                            extOption.type,
                            extOption
                        );
                        extension && extensions.push(extension);
                    }
                }

                // 绑定视图环境和控件主元素
                controlOptions.viewContext = options.viewContext;
                // 容器类控件会需要渲染自己的`innerHTML`，
                // 这种渲染使用`initChildren`，再调用`main.init`，
                // 因此需要把此处`main.init`的参数交给控件，方便再带回来，
                // 以便`properties`、`valueReplacer`之类的能保留
                controlOptions.renderOptions = options;
                controlOptions.main = element;
                if (typeof options.group !== 'undefined') {
                    controlOptions.group = options.group;
                }

                // 创建控件
                var control = main.create(type, controlOptions);
                if (control) {
                    controls.push(control);
                    if (options.parent) {
                        options.parent.addChild(control);
                    }
                    try {
                        control.render();
                    }
                    catch (ex) {
                        var error = new Error(
                            'Render control '
                                + '"' + (control.id || 'anonymous') + '" '
                                + 'of type ' + control.type + ' '
                                + 'failed because: '
                                + ex.message
                        );
                        error.actualError = ex;
                        throw error;
                    }
                }
            }
        }

        return controls;
    };
    /* eslint-enable fecs-max-statements */

    // 导入register相关的代码，因为要做alias
    //

    /**
     * 控件类容器
     *
     * @type {Object}
     * @ignore
     */
    var controlClasses = {};

    /**
     * 注册控件类
     *
     * 该方法通过类的`prototype.type`识别控件类型信息。
     *
     * @param {Function} controlClass 控件类
     * @throws
     * 已经有相同`prototype.type`的控件类存在，不能重复注册同类型控件
     */
    main.register = function (controlClass) {
        registerClass(controlClass, controlClasses);
    };

    /**
     * 用别名注册一个控件
     * @param {string} aliasName 别名
     * @param {Control} control 控件类
     * @return {Control} 直接返回control
     */
    main.alias = function (aliasName, control) {
        registerAlias(aliasName, control, controlClasses);
        return control;
    };

    /**
     * 给控件一个别名。可以根据别名找到控件，但控件本身的type仍不变。
     *
     * @param {string} aliasName 控件别名
     * @param {Function} classFunc 控件类
     * @param {Object} container 类容器
     */
    function registerAlias(aliasName, classFunc, container) {
        if (typeof classFunc === 'function') {
            if (aliasName in container) {
                throw new Error(aliasName + ' is exists!');
            }

            container[aliasName] = classFunc;
        }
    }

   /**
     * 注册类。用于控件类、规则类或扩展类注册
     *
     * @param {Function} classFunc 类Function
     * @param {Object} container 类容器
     * @ignore
     */
    function registerClass(classFunc, container) {
        if (typeof classFunc === 'function') {
            var type = classFunc.prototype.type;
            if (type in container) {
                throw new Error(type + ' is exists!');
            }

            container[type] = classFunc;
        }
    }

    /**
     * 扩展类容器
     *
     * @type {Object}
     * @ignore
     */
    var extensionClasses = {};

    /**
     * 注册扩展类。
     *
     * 该方法通过类的`prototype.type`识别扩展类型信息
     *
     * @param {Function} extensionClass 扩展类
     * @throws
     * 已经有相同`prototype.type`的扩展类存在，不能重复注册同类型扩展
     */
    main.registerExtension = function (extensionClass) {
        registerClass(extensionClass, extensionClasses);
    };

    var esuiCreateExtension = main.createExtension;

    /**
     * 创建扩展
     *
     * @param {string} type 扩展类型
     * @param {Object} options 初始化参数
     * @return {Extension}
     */
    main.createExtension = function (type, options) {
        // 若有扩展先于FCUI main初始化时register，则这些扩展会放在ESUI的闭包classes中，
        // 这里若本地FCUI找不到想要的扩展，则回退到ESUI中。
        return createInstance(type, options, extensionClasses)
            || esuiCreateExtension(type, options);
    };

    /**
     * 验证规则类容器
     *
     * @type {Object}
     * @ignore
     */
    var ruleClasses = [];

    /**
     * 注册控件验证规则类
     *
     * 该方法通过类的`prototype.type`识别验证规则类型信息
     *
     * @param {Function} ruleClass 验证规则类
     * @param {number} priority 优先级，越小的优先级越高
     * @throws
     * 已经有相同`prototype.type`的验证规则类存在，不能重复注册同类型验证规则
     */
    main.registerRule = function (ruleClass, priority) {
        // 多个Rule共享一个属性似乎也没问题
        ruleClasses.push({type: ruleClass, priority: priority});
        // 能有几个规则，这里就不优化为插入排序了
        ruleClasses.sort(
            function (x, y) {
                return x.priority - y.priority;
            }
        );
    };

    /**
     * 创建控件实例需要的验证规则
     *
     * @param {Control} control 控件实例
     * @return {validator.Rule[]} 验证规则数组
     */
    main.createRulesByControl = function (control) {
        var rules = [];
        for (var i = 0; i < ruleClasses.length; i++) {
            var RuleClass = ruleClasses[i].type;
            if (control.get(RuleClass.prototype.type) != null) {
                rules.push(new RuleClass());
            }
        }

        return rules;
    };

    /**
     * 创建类实例。用于控件类、规则类或扩展类的实例创建
     *
     * @param {string} type 类型
     * @param {Object} options 初始化参数
     * @param {Object} container 类容器
     * @ignore
     * @return {Control} 创建的控件
     */
    function createInstance(type, options, container) {
        var Constructor = container[type];
        if (Constructor) {
            delete options.type;
            return new Constructor(options);
        }

        return null;
    }

    var esuiCreate = main.create;

    /**
     * 创建控件
     *
     * @param {string} type 控件类型
     * @param {Object} options 初始化参数
     * @return {Control}
     */
    main.create = function (type, options) {
        // 若有控件先于FCUI main初始化时register，则这些控件会放在ESUI的闭包classes中，
        // 这里若本地FCUI找不到想要的控件，则回退到ESUI中。
        return createInstance(type, options, controlClasses)
            || esuiCreate(type, options);
    };

    main.version = '0.0.2-alpha.33';

    return main;
});
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var db = require('../../config/sequelize'),
    _ = require('lodash');

/**
 * Find klass by id
 * Note: This is called every time that the parameter :klassId is used in a URL.
 * Its purpose is to preload the klass on the req object then call the next function.
 */
exports.klass = function(req, res, next, id) {
    console.log('id => ' + id);
    db.Klass.find({ where: {id: id}, include: [db.User, db.Category]}).success(function(klass){
        if(!klass) {
            return next(new Error('Failed to load klass ' + id));
        } else {
            req.klass = klass;
            return next();
        }
    }).error(function(err){
        return next(err);
    });
};

/**
 * Create a klass
 */
exports.create = function(req, res) {
    // augment the klass by adding the UserId
    req.body.UserId = req.user.id;
    // save and return and instance of klass on the res object.
    db.Klass.create(req.body).success(function(klass){
        if(!klass){
            return res.send('users/signup', {errors: err});
        } else {
            return res.jsonp(klass);
        }
    }).error(function(err){
        return res.send('users/signup', {
            errors: err,
            status: 500
        });
    });
};

/**
 * Update a klass
 */
exports.update = function(req, res) {

    // create a new variable to hold the klass that was placed on the req object.
    var klass = req.klass;

    klass.updateAttributes({
        title: req.body.title,
        content: req.body.content
    }).success(function(a){
        return res.jsonp(a);
    }).error(function(err){
        return res.render('error', {
            error: err,
            status: 500
        });
    });
};

/**
 * Delete an klass
 */
exports.destroy = function(req, res) {

    // create a new variable to hold the klass that was placed on the req object.
    var klass = req.klass;

    klass.destroy().success(function(){
        return res.jsonp(klass);
    }).error(function(err){
        return res.render('error', {
            error: err,
            status: 500
        });
    });
};

/**
 * Show an klass
 */
exports.show = function(req, res) {
    // Sending down the klass that was just preloaded by the klasses.klass function
    // and saves klass on the req object.
    return res.jsonp(req.klass);
};

/**
 * List of Klasses
 */
exports.all = function(req, res) {
    var params = {include: [db.User, db.Category], where: {}};

    if(req.query.offset) params.offset = req.query.offset;
    if(req.query.limit) params.limit = req.query.limit;
    if(req.query.withTransactions) {
        var incTrans = {
            model: db.Transaction
        };
        params.include.push(incTrans);
    }

    if(req.query.fromDate && req.query.toDate){
        params.where['Transactions.date'] = {between: [
            new Date(req.query.fromDate),
            new Date(req.query.toDate)
        ]};
    }

    db.Klass.findAll(params).success(function(klasses){
        return res.jsonp(klasses);
    }).error(function(err){
        return res.render('500', {
            error: err,
            status: 500
        });
    });
};<commit_msg>added a little optimization in the klass query<commit_after>/**
 * Module dependencies.
 */
var db = require('../../config/sequelize'),
    _ = require('lodash');

/**
 * Find klass by id
 * Note: This is called every time that the parameter :klassId is used in a URL.
 * Its purpose is to preload the klass on the req object then call the next function.
 */
exports.klass = function(req, res, next, id) {
    console.log('id => ' + id);
    db.Klass.find({ where: {id: id}, include: [db.User, db.Category]}).success(function(klass){
        if(!klass) {
            return next(new Error('Failed to load klass ' + id));
        } else {
            req.klass = klass;
            return next();
        }
    }).error(function(err){
        return next(err);
    });
};

/**
 * Create a klass
 */
exports.create = function(req, res) {
    // augment the klass by adding the UserId
    req.body.UserId = req.user.id;
    // save and return and instance of klass on the res object.
    db.Klass.create(req.body).success(function(klass){
        if(!klass){
            return res.send('users/signup', {errors: err});
        } else {
            return res.jsonp(klass);
        }
    }).error(function(err){
        return res.send('users/signup', {
            errors: err,
            status: 500
        });
    });
};

/**
 * Update a klass
 */
exports.update = function(req, res) {

    // create a new variable to hold the klass that was placed on the req object.
    var klass = req.klass;

    klass.updateAttributes({
        title: req.body.title,
        content: req.body.content
    }).success(function(a){
        return res.jsonp(a);
    }).error(function(err){
        return res.render('error', {
            error: err,
            status: 500
        });
    });
};

/**
 * Delete an klass
 */
exports.destroy = function(req, res) {

    // create a new variable to hold the klass that was placed on the req object.
    var klass = req.klass;

    klass.destroy().success(function(){
        return res.jsonp(klass);
    }).error(function(err){
        return res.render('error', {
            error: err,
            status: 500
        });
    });
};

/**
 * Show an klass
 */
exports.show = function(req, res) {
    // Sending down the klass that was just preloaded by the klasses.klass function
    // and saves klass on the req object.
    return res.jsonp(req.klass);
};

/**
 * List of Klasses
 */
exports.all = function(req, res) {
    var params = {
        include: [{
                model: db.User,
                attributes: ['id']
            }, {
                model: db.Category,
                attributes: ['KlassId', 'id', 'name']
            }
        ],
        where: {}
    };

    if(req.query.offset) params.offset = req.query.offset;
    if(req.query.limit) params.limit = req.query.limit;
    if(req.query.withTransactions) {
        var incTrans = {
            model: db.Transaction,
            attributes: ['amount', 'description', 'KlassId', 'CategoryId']
        };
        params.include.push(incTrans);
    }

    if(req.query.fromDate && req.query.toDate){
        params.where['Transactions.date'] = {between: [
            new Date(req.query.fromDate),
            new Date(req.query.toDate)
        ]};
    }

    db.Klass.findAll(params).success(function(klasses){
        return res.jsonp(klasses);
    }).error(function(err){
        return res.render('500', {
            error: err,
            status: 500
        });
    });
};<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';
import {Card, CardActions, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import keycode from 'keycode'

import Git from 'nodegit';

// var pathToRepo = require('path').resolve('../js-reporters');

var getCommits = function(repo) {

  var pathToRepo = require('path').resolve(repo);
  Git.Repository.open(pathToRepo)
  .then(function(repo) {
    return repo.getMasterCommit();
  })
  .then(function( firstMasterCommit ) {
    var history = firstMasterCommit.history( Git.Revwalk.SORT.Time);
    history.on("end", walk);
    history.start();
  })
  .done();

  //   function passed to history.on() listener to receive commits' info.
  //   calls the setCommitState of App class to set new state
  var walk = (function(commitsArr) {
    var commits = [];
      commitsArr.forEach( function(commit) {
        commits.push({
          sha: commit.sha(),
          author: commit.author().name(),
          email: commit.author().email(),
          date: commit.date().toString(),
          message: commit.message()
        });
      });

      this.getCommits(commits, repo);
  }).bind(this);

}

const darkMuiTheme = getMuiTheme(darkBaseTheme);

class Commit extends React.Component {
  render() {
    var commit = this.props.commit;
    return (

        <Card style={ {margin:5} } >
          <CardHeader title={commit.author} subtitle={commit.message}
          actAsExpander={true}
          showExpandableButton={true} />
          <CardText  expandable={true} >

{commit.email}

{commit.sha}

{commit.date}

          </CardText>
        </Card>

    )
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.state={ value:props.pathToRepo };
    this.updateValue = this.updateValue.bind(this);
    this.sendQuery = this.sendQuery.bind(this);
  }

  updateValue(event) {
    this.setState({
      value: event.target.value
    });
  }

  sendQuery(event) {
    console.log(event.which);
    if(!event.which || (event.which && event.which === 13) )
      this.props.onKeyPress(this.state.value);
  }

  render() {
    const style = {
      margin:10
    };

    return (

        <input type="text" placeholder="Enter Repo name here..." ref="textField"
        value={this.state.value} onChange={this.updateValue} onKeyDown={this.sendQuery} />
        <RaisedButton label="Get Commits!"  onMouseDown={this.sendQuery} />

    )
  }
}

class CommitTable extends React.Component {
  render() {

    const style={
      border:'1px solid black',
      overflow: 'auto',
      width: '100%'
    };

    let rows=[];
    this.props.commits.forEach(function(commit, index) {
      rows.push(<Commit commit={commit} key={index} />);
    });
    return (

{ rows }

    )
  }
}

class RepoTable extends React.Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(obj) {
    // this.handleRepoClick(this.)
    console.log(obj.target);
  }

  render() {
    const style= {
      margin: 5,
    };

    let rows = [];
    this.props.repos.forEach((function(repo, index) {
        rows.push(
          <Card style={style} key={index} onMouseDown={this.handleClick} >
            <CardHeader title={repo} />
          </Card>
        );
    }).bind(this));
    return (

{rows}

    )
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      pathToRepo:'' ,
      commits: [] ,
      repos: []
    };
    this.handleKeyPressChange = this.handleKeyPressChange.bind(this);
    this.handleRepoClick = this.handleRepoClick.bind(this);
  }

  handleKeyPressChange(passedPath)
  {
    (getCommits.bind(this))(passedPath);
  }

  getCommits(commits, repo)
  {
    var repos = this.state.repos;
    repo = repo.slice(3);
    if(repos.indexOf(repo) == -1)
      repos.push(repo);
    this.setState( {
      commits: commits,
      repos: repos
    });
    console.log("AFTER"+this.state.commits, this.state.repos);
  }

  handleRepoClick() {

  }

  render() {

    const style = {
      display: 'flex',
      border: '1px solid black',
      width:800,
      height:400,
    };

    return (
      <MuiThemeProvider muiTheme={darkMuiTheme}>

          <SearchBar pathToRepo={this.state.pathToRepo} onKeyPress={this.handleKeyPressChange} />

            <RepoTable repos={this.state.repos} />

            <CommitTable commits={this.state.commits} selectRepo={this.handleRepoClick} />

      </MuiThemeProvider>
    )
  }

}

ReactDOM.render(
  <App  />,
  document.getElementById('app')
);
<commit_msg>View: Can view commits by click repo's card<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';
import {Card, CardActions, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import keycode from 'keycode'

import Git from 'nodegit';

// var pathToRepo = require('path').resolve('../js-reporters');

var getCommitsFromRepo = function(repo) {

  var pathToRepo = require('path').resolve(repo);
  Git.Repository.open(pathToRepo)
  .then(function(repo) {
    return repo.getMasterCommit();
  })
  .then(function( firstMasterCommit ) {
    var history = firstMasterCommit.history( Git.Revwalk.SORT.Time);
    history.on("end", walk);
    history.start();
  })
  .done();

  //   function passed to history.on() listener to receive commits' info.
  //   calls the setCommitState of App class to set new state
  var walk = (function(commitsArr) {
    var commits = [];
      commitsArr.forEach( function(commit) {
        commits.push({
          sha: commit.sha(),
          author: commit.author().name(),
          email: commit.author().email(),
          date: commit.date().toString(),
          message: commit.message()
        });
      });

      let newRepo = {
        repoName:repo.slice(3),
        commits: commits
      };

      this.returnRepo(newRepo);
  }).bind(this);

}

const darkMuiTheme = getMuiTheme(darkBaseTheme);

class Commit extends React.Component {
  render() {
    var commit = this.props.commit;
    return (

        <Card style={ {margin:5} } >
          <CardHeader title={commit.author} subtitle={commit.message}
          actAsExpander={true}
          showExpandableButton={true} />
          <CardText  expandable={true} >

{commit.email}

{commit.sha}

{commit.date}

          </CardText>
        </Card>

    )
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.state={ value:props.pathToRepo };
    this.updateValue = this.updateValue.bind(this);
    this.sendQuery = this.sendQuery.bind(this);
  }

  updateValue(event) {
    this.setState({
      value: event.target.value
    });
  }

  sendQuery(event) {
    if(!event.which || (event.which && event.which === 13) )
      this.props.onKeyPress(this.state.value);
  }

  render() {
    const style = {
      margin:10
    };

    return (

        <input type="text" placeholder="Enter Repo name here..." ref="textField"
        value={this.state.value} onChange={this.updateValue} onKeyDown={this.sendQuery} />
        <RaisedButton label="Get Commits!"  onMouseDown={this.sendQuery} />

    )
  }
}

class CommitTable extends React.Component {
  render() {

    const style={
      border:'1px solid black',
      overflow: 'auto',
      width: '100%'
    };

    let rows=[];
    this.props.commits.forEach(function(commit, index) {
      rows.push(<Commit commit={commit} key={index} />);
    });
    return (

{ rows }

    )
  }
}

class RepoTable extends React.Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(i) {
    this.props.selectRepo(i);
  }

  render() {
    const style= {
      margin: 5,
    };

    let rows = [];
    this.props.repos.forEach((function(repo, index) {
        rows.push(
          <Card style={style} key={index} onMouseDown={this.handleClick.bind(this, index)} >
            <CardHeader title={repo.repoName} />
          </Card>
        );
    }).bind(this));
    return (

{rows}

    )
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      pathToRepo:'' ,
      currentRepo: '',
      currentRepoCommits: [],
      repos: [],
      repoNames: []
    };
    this.handleKeyPressChange = this.handleKeyPressChange.bind(this);
    this.handleRepoClick = this.handleRepoClick.bind(this);
    this.setCurrentCommits = this.setCurrentCommits.bind(this);
    this.returnRepo = this.returnRepo.bind(this);
  }

  handleKeyPressChange(passedPath)
  {
    let repoName = passedPath.slice(3);
    if(this.state.repoNames.indexOf(repoName) == -1) {
      //call to global function
      (getCommitsFromRepo.bind(this))(passedPath);
    }
    else {

    }
  }

  setCurrentCommits(repo) {
    this.setState({
      currentRepo: repo.repoName,
      currentRepoCommits: repo.commits
    });
  }

  handleRepoClick(index) {
    this.setCurrentCommits(this.state.repos[index]);
  }

  returnRepo(newRepo)
  {
    let repos = this.state.repos;
    let repoNames = this.state.repoNames;
    repos.push(newRepo);
    repoNames.push(newRepo.repoName);
    this.setState({
      repos: repos,
      repoNames: repoNames
    });
    this.setCurrentCommits(newRepo);
  }

  render() {

    const style = {
      display: 'flex',
      border: '1px solid black',
      width:800,
      height:400,
    };

    return (
      <MuiThemeProvider muiTheme={darkMuiTheme}>

          <SearchBar pathToRepo={this.state.pathToRepo} onKeyPress={this.handleKeyPressChange} />

            <RepoTable repos={this.state.repos} selectRepo={this.handleRepoClick} />

            <CommitTable commits={this.state.currentRepoCommits}  />

      </MuiThemeProvider>
    )
  }

}

ReactDOM.render(
  <App  />,
  document.getElementById('app')
);
<|endoftext|>