<commit_before>module.exports = {
	dbConnector: null,
	router: null,
	fileSystem: null,
	rootRequestHandler: function (fileSystem) {
		return function (q,s) {
			fileSystem.readFile('./portal/index.html',function(e,d){
				if (e) {
					console.log("There was an error: " + e);
					var body = "There was an error: " + e;
					s.setHeader('Content-Type', 'text/plain');
					s.setHeader('Content-Length', body.length);
					s.send(body);
				} else {
					s.setHeader('Content-Type', 'text/html');
					s.setHeader('Content-Length', d.length);
					s.send(d);
				}
			});
		}
	},
	basicErrorHandler: function (q,s) {
		return function (e) {
			var json = "";
			if (e) {
				json = "{'error':'"+e+"'}";
			} else {
				json = "{'success':'true'}";
			}
			s.setHeader('Content-Type', 'text/json');
			s.setHeader('Content-Length', json.length);
			s.send(json);
		}
	},
	materialsRequestHandler: function (material) {
		return function (q,s) {
			return material.find(function(e,d){
				var json = "";
				if (e) {
					json = "{'error':'"+e+"'}";
					s.setHeader('Content-Type','text/json');
					s.setHeader('Content-Length',json.length);
					s.send(json);
				} else {
					json = "{'materials':[";
					d.forEach(function(m){
						json += "{'_id':'"+m._id+"','mname':'"+m.mname+"','mdescription':'"+m.mdescription+"','munit':'"+m.munit+"','mcount':'"+m.mcount+"'}";
					});
					json += "]}";
					s.setHeader('Content-Type','text/json');
					s.setHeader('Content-Length',json.length);
					s.send(json);
				}
			});
		}
	},
	createMaterialRequestHandler: function (material,basicErrorHandler) {
		return function (q,s) {
			var m = new material({
				mname: q.body.mname,
				mdescription: q.body.mdescription,
				munit: q.body.munit,
				mcount: q.body.mcount
			});
			m.save(basicErrorHandler(q,s));
		}
	},
	updateMaterialByIdRequestHandler: function (material,basicErrorHandler) {
		return function (q,s) {
			return material.findByIdAndUpdate(q.body._id,{
					mname: q.body.mname,
					mdescription: q.body.mdescription,
					munit: q.body.munit,
					mcount: q.body.mcount
				},
				basicErrorHandler(q,s)
			);
		}
	},
	deleteMaterialById: function (material,basicErrorHandler) {
		return function (q,s) {
			return material.findByIdAndRemove(q.body._id,basicErrorHandler(q,s));
		}
	},
	init: function (dbConnector,router) {
		this.dbConnector = dbConnector;
		this.router = router;
		this.fileSystem = require('fs');
		this.router.get('/',this.rootRequestHandler(this.fileSystem));
		this.router.get('/api/materials',this.materialsRequestHandler(this.dbConnector.schemaModels.models.material));
		this.router.post('/api/cr/material',this.createMaterialRequestHandler(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
		this.router.put('/api/u/material',this.updateMaterialByIdRequestHandler(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
		this.router.delete('/api/d/material',this.deleteMaterialById(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
	}
}<commit_msg>refactored request error handlers<commit_after>module.exports = {
	dbConnector: null,
	router: null,
	fileSystem: null,
	rootRequestErrorHandler: function (q,s) {
		return function (e,d) {
			if (e) {
				var body = "There was an error: " + e;
				s.setHeader('Content-Type', 'text/plain');
				s.setHeader('Content-Length', body.length);
				s.send(body);
			} else {
				s.setHeader('Content-Type', 'text/html');
				s.setHeader('Content-Length', d.length);
				s.send(d);
			}
		}
	},
	basicErrorHandler: function (q,s) {
		return function (e) {
			var json = "";
			if (e) {
				json = "{'error':'"+e+"'}";
			} else {
				json = "{'success':'true'}";
			}
			s.setHeader('Content-Type', 'text/json');
			s.setHeader('Content-Length', json.length);
			s.send(json);
		}
	},
	materialsRequestErrorHandler: function(q,s) {
		return function(e,d){
			var json = "";
			if (e) {
				json = "{'error':'"+e+"'}";
			} else {
				json = "{'materials':[";
				d.forEach(function(m){
					json += "{'_id':'"+m._id+"','mname':'"+m.mname+"','mdescription':'"+m.mdescription+"','munit':'"+m.munit+"','mcount':'"+m.mcount+"'}";
				});
				json += "]}";
			}
			s.setHeader('Content-Type','text/json');
			s.setHeader('Content-Length',json.length);
			s.send(json);
		}
	},
	rootRequestHandler: function (fileSystem,rootRequestErrorHandler) {
		return function (q,s) {
			return fileSystem.readFile('./portal/index.html',rootRequestErrorHandler(q,s));
		}
	},
	materialsRequestHandler: function (material,materialsRequestErrorHandler) {
		return function (q,s) {
			return material.find(materialsRequestErrorHandler(q,s));
		}
	},
	createMaterialRequestHandler: function (material,basicErrorHandler) {
		return function (q,s) {
			return new material({
				mname: q.body.mname,
				mdescription: q.body.mdescription,
				munit: q.body.munit,
				mcount: q.body.mcount
			}).save(basicErrorHandler(q,s));
		}
	},
	updateMaterialByIdRequestHandler: function (material,basicErrorHandler) {
		return function (q,s) {
			return material.findByIdAndUpdate(q.body._id,{
					mname: q.body.mname,
					mdescription: q.body.mdescription,
					munit: q.body.munit,
					mcount: q.body.mcount
				},
				basicErrorHandler(q,s)
			);
		}
	},
	deleteMaterialById: function (material,basicErrorHandler) {
		return function (q,s) {
			return material.findByIdAndRemove(q.body._id,basicErrorHandler(q,s));
		}
	},
	init: function (dbConnector,router) {
		this.dbConnector = dbConnector;
		this.router = router;
		this.fileSystem = require('fs');
		this.router.get('/',this.rootRequestHandler(this.fileSystem,this.rootRequestErrorHandler));
		this.router.get('/api/materials',this.materialsRequestHandler(this.dbConnector.schemaModels.models.material,this.materialsRequestErrorHandler));
		this.router.post('/api/cr/material',this.createMaterialRequestHandler(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
		this.router.put('/api/u/material',this.updateMaterialByIdRequestHandler(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
		this.router.delete('/api/d/material',this.deleteMaterialById(this.dbConnector.schemaModels.models.material,this.basicErrorHandler));
	}
}<|endoftext|>


<commit_before>// Dependencies

let isArray = require('lodash/lang/isArray');
let keys = require('lodash/object/keys');
let checkIsNotNull = require('focus').util.object.checkIsNotNull;
let ArgumentNullException = require('focus').exception.ArgumentNullException;

// Components

let SingleGroupComponent = require('./single-group-component').component;
let ListSelection = require('../../../list/selection').list.component;

// Mixins

let i18nMixin = require('../../../common/i18n/mixin');

//Empty Result component to be mututalized.
let EmptyComponent = React.createClass({
    mixins: [i18nMixin],
    render(){
        return (

                    {this.i18n('search.empty')}

            );
    }
});

/**
 * Mixin used in order to create a block.
 * @type {Object}
 */
let GroupByMixin = {
    mixins: [i18nMixin],
    /**
     * Init default props.
     * @returns {object} Default props.
     */
    getDefaultProps() {
        return ({
            lineOperationList: [],
            orderableColumnList: {},
            lineComponentMapper() {
                throw new ArgumentNullException('Please provide a inferLineComponentFromList(list) function.');
            },
            emptyComponent: EmptyComponent
        });
    },
    /**
     * Change the max rows of a group.
     * @param {string} groupKey Key of the group.
     * @param {int} maxRows Number of needed rows.
     * @returns {Function} The function wich will change the max rows of the group.
     */
    changeGroupByMaxRows(groupKey, maxRows) {
        return (event) => {
            this.refs[groupKey].changeGroupByMaxRows(maxRows);
        };
    },
    getResultListComponent(advancedSearch) {
        // First check if there is any result
        if (this.state.totalRecords === 0) {
            return this.props.emptyComponent;
        }
        let isBounded = keys(this.state.map).length > 1;
        let noDecoration = keys(this.state.map).length === 1;
        if (noDecoration && advancedSearch) {
            let groupKey = keys(this.state.map)[0];
            return this._getSingleTypeResultList(groupKey, this.state.map[groupKey], isBounded && this.props.groupMaxRows);
        } else {
            let groupList = keys(this.state.map).map((groupKey) => {
                return (
                    <SingleGroupComponent
                        data-focus='results-group'
                        key={groupKey}
                        ref={groupKey}
                        renderGroupBy={this.renderGroupByBlock}
                        list={this.state.map[groupKey]}
                        groupKey={groupKey}
                        maxRows={isBounded && this.props.groupMaxRows}
                        />
                );
            });
            return groupList;
        }
    },
    _getSingleTypeResultList(groupKey, list, maxRows) {
        if (list.length === 0) {
            return <this.props.emptyComponent />;
        }
        if (maxRows) {
            list = list.slice(0, maxRows);
        }
        return (
            <ListSelection
                data-focus='results-list'
                data={list}
                ref={groupKey}
                idField={this.props.idField}
                isSelection={this.props.isSelection}
                onSelection={this._selectItem}
                onLineClick={this._lineClick}
                fetchNextPage={this.fetchNextPage}
                hasMoreData={this.state.hasMoreData}
                isLoading={this.state.isLoading}
                operationList={this.props.lineOperationList}
                lineComponent={this.props.lineComponentMapper(list)}
                parentSelector={this.props.parentSelector}
                selectionStatus={this.state.selectionStatus}
                />
        );
    },
    renderGroupByBlock(groupKey, list, maxRows) {
        let GroupWrapper = this.props.groupComponent;
        return (
            <GroupWrapper data-focus="group-result-container" groupKey={groupKey} query={this.state.query} showAll={this.changeGroupByMaxRows}>
                {this._getSingleTypeResultList(groupKey, list, maxRows)}
            </GroupWrapper>
        );
    }
};

module.exports = {mixin: GroupByMixin};
<commit_msg>[group-by-mixin] Fix show all prop<commit_after>// Dependencies

let isArray = require('lodash/lang/isArray');
let keys = require('lodash/object/keys');
let checkIsNotNull = require('focus').util.object.checkIsNotNull;
let ArgumentNullException = require('focus').exception.ArgumentNullException;

// Components

let SingleGroupComponent = require('./single-group-component').component;
let ListSelection = require('../../../list/selection').list.component;

// Mixins

let i18nMixin = require('../../../common/i18n/mixin');

//Empty Result component to be mututalized.
let EmptyComponent = React.createClass({
    mixins: [i18nMixin],
    render(){
        return (

                    {this.i18n('search.empty')}

            );
    }
});

/**
 * Mixin used in order to create a block.
 * @type {Object}
 */
let GroupByMixin = {
    mixins: [i18nMixin],
    /**
     * Init default props.
     * @returns {object} Default props.
     */
    getDefaultProps() {
        return ({
            lineOperationList: [],
            orderableColumnList: {},
            lineComponentMapper() {
                throw new ArgumentNullException('Please provide a inferLineComponentFromList(list) function.');
            },
            emptyComponent: EmptyComponent
        });
    },
    /**
     * Change the max rows of a group.
     * @param {string} groupKey Key of the group.
     * @param {int} maxRows Number of needed rows.
     * @returns {Function} The function wich will change the max rows of the group.
     */
    changeGroupByMaxRows(groupKey, maxRows) {
        return (event) => {
            this.refs[groupKey].changeGroupByMaxRows(maxRows);
        };
    },
    getResultListComponent(advancedSearch) {
        // First check if there is any result
        if (this.state.totalRecords === 0) {
            return this.props.emptyComponent;
        }
        let isBounded = keys(this.state.map).length > 1;
        let noDecoration = keys(this.state.map).length === 1;
        if (noDecoration && advancedSearch) {
            let groupKey = keys(this.state.map)[0];
            return this._getSingleTypeResultList(groupKey, this.state.map[groupKey], isBounded && this.props.groupMaxRows);
        } else {
            let groupList = keys(this.state.map).map((groupKey) => {
                return (
                    <SingleGroupComponent
                        data-focus='results-group'
                        key={groupKey}
                        ref={groupKey}
                        renderGroupBy={this.renderGroupByBlock}
                        list={this.state.map[groupKey]}
                        groupKey={groupKey}
                        maxRows={isBounded && this.props.groupMaxRows}
                        />
                );
            });
            return groupList;
        }
    },
    _getSingleTypeResultList(groupKey, list, maxRows) {
        if (list.length === 0) {
            return <this.props.emptyComponent />;
        }
        if (maxRows) {
            list = list.slice(0, maxRows);
        }
        return (
            <ListSelection
                data-focus='results-list'
                data={list}
                ref={groupKey}
                idField={this.props.idField}
                isSelection={this.props.isSelection}
                onSelection={this._selectItem}
                onLineClick={this._lineClick}
                fetchNextPage={this.fetchNextPage}
                hasMoreData={this.state.hasMoreData}
                isLoading={this.state.isLoading}
                operationList={this.props.lineOperationList}
                lineComponent={this.props.lineComponentMapper(list)}
                parentSelector={this.props.parentSelector}
                selectionStatus={this.state.selectionStatus}
                />
        );
    },
    renderGroupByBlock(groupKey, list, maxRows) {
        let GroupWrapper = this.props.groupComponent;
        return (
            <GroupWrapper data-focus="group-result-container" groupKey={groupKey} query={this.state.query} showAll={this.showAllGroupListHandler}>
                {this._getSingleTypeResultList(groupKey, list, maxRows)}
            </GroupWrapper>
        );
    }
};

module.exports = {mixin: GroupByMixin};
<|endoftext|>


<commit_before>/*
Name:           geoip2ws
Description:    Maxmind GeoIP2 Web Services for Node.js
Source & docs:  https://github.com/fvdm/nodejs-geoip2ws
Feedback:       https://github.com/fvdm/nodejs-geoip2ws/issues
License:        Public Domain / Unlicense

This product includes GeoLite2 data created by MaxMind, available from
<http://www.maxmind.com>.
*/

var https = require('https')

// setup
var app = {
	userId: null,
	licenseKey: null,
	service: 'city',
	requestTimeout: 5000
}

module.exports = function( userId, licenseKey, service, requestTimeout ) {
	if( userId === undefined || licenseKey === undefined ) {
		return new Error('no userId or licenseKey')
	}

	app.userId = userId
	app.licenseKey = licenseKey

	if( typeof service === 'number' ) {
		app.requestTimeout = service
	} else {
		app.service = service || app.service
		app.requestTimeout = requestTimeout || app.requestTimeout
	}

	return function( service, ip, callback ) {

		// service is optional
		if( arguments.length === 2 ) {
			var callback = ip
			var ip = service
			var service = app.service
		}

		// prevent multiple callbacks
		var complete = false
		function doCallback( err, res ) {
			if( ! complete ) {
				complete = true
				callback( err, res )
			}
		}

		// build request
		var options = {
			hostname: 'geoip.maxmind.com',
			path: '/geoip/v2.1/'+ service +'/'+ ip,
			agent: false,
			headers: {
				'Accept': 'application/json',
				'User-Agent': 'https://github.com/fvdm/nodejs-geoip2ws'
			},
			auth: app.userId +':'+ app.licenseKey
		}

		var request = https.request( options )

		// request response
		request.on( 'response', function( response ) {
			var data = []
			var size = 0
			var err = null

			// collect data
			response.on( 'data', function( chunk ) {
				data.push( chunk )
				size += chunk.length
			})

			// process data
			response.on( 'end', function() {
				if( data.length >= 1 ) {
					var buf = new Buffer( size )
					var pos = 0

					for( var d in data ) {
						data[d].copy( buf, pos )
						pos += data[d].length
					}

					data = data.toString('utf8').trim()

					try {
						data = JSON.parse( data )

						if( data.error !== undefined ) {
							err = new Error('API error')
							err.code = data.code
							err.error = data.error
						}
					} catch(e) {
						err = new Error('not json')
					}
				} else {
					err = new Error('no data')
				}

				doCallback( err, data )
			})

			// connection dropped
			response.on( 'close', function() {
				doCallback( new Error('request dropped') )
			})
		})

		request.on( 'socket', function( socket ) {
			if( app.requestTimeout !== undefined ) {
				socket.setTimeout( app.requestTimeout )
				socket.on( 'timeout', function() {
					request.abort()
					doCallback( new Error('request timeout') )
				})
			}
		})

		// request error
		request.on( 'error', function( error ) {
			var err = new Error('request failed')
			err.error = error
			doCallback( err )
		})

		// request finished
		request.end()

	}
}
<commit_msg>Use native for..loop<commit_after>/*
Name:           geoip2ws
Description:    Maxmind GeoIP2 Web Services for Node.js
Source & docs:  https://github.com/fvdm/nodejs-geoip2ws
Feedback:       https://github.com/fvdm/nodejs-geoip2ws/issues
License:        Public Domain / Unlicense

This product includes GeoLite2 data created by MaxMind, available from
<http://www.maxmind.com>.
*/

var https = require('https')

// setup
var app = {
	userId: null,
	licenseKey: null,
	service: 'city',
	requestTimeout: 5000
}

module.exports = function( userId, licenseKey, service, requestTimeout ) {
	if( userId === undefined || licenseKey === undefined ) {
		return new Error('no userId or licenseKey')
	}

	app.userId = userId
	app.licenseKey = licenseKey

	if( typeof service === 'number' ) {
		app.requestTimeout = service
	} else {
		app.service = service || app.service
		app.requestTimeout = requestTimeout || app.requestTimeout
	}

	return function( service, ip, callback ) {

		// service is optional
		if( arguments.length === 2 ) {
			var callback = ip
			var ip = service
			var service = app.service
		}

		// prevent multiple callbacks
		var complete = false
		function doCallback( err, res ) {
			if( ! complete ) {
				complete = true
				callback( err, res )
			}
		}

		// build request
		var options = {
			hostname: 'geoip.maxmind.com',
			path: '/geoip/v2.1/'+ service +'/'+ ip,
			agent: false,
			headers: {
				'Accept': 'application/json',
				'User-Agent': 'https://github.com/fvdm/nodejs-geoip2ws'
			},
			auth: app.userId +':'+ app.licenseKey
		}

		var request = https.request( options )

		// request response
		request.on( 'response', function( response ) {
			var data = []
			var size = 0
			var err = null

			// collect data
			response.on( 'data', function( chunk ) {
				data.push( chunk )
				size += chunk.length
			})

			// process data
			response.on( 'end', function() {
				if( data.length >= 1 ) {
					var buf = new Buffer( size )
					var pos = 0

					for( var i=0; i<data.length; i++ ) {
						data[i].copy( buf, pos )
						pos += data[i].length
					}

					data = data.toString('utf8').trim()

					try {
						data = JSON.parse( data )

						if( data.error !== undefined ) {
							err = new Error('API error')
							err.code = data.code
							err.error = data.error
						}
					} catch(e) {
						err = new Error('not json')
					}
				} else {
					err = new Error('no data')
				}

				doCallback( err, data )
			})

			// connection dropped
			response.on( 'close', function() {
				doCallback( new Error('request dropped') )
			})
		})

		request.on( 'socket', function( socket ) {
			if( app.requestTimeout !== undefined ) {
				socket.setTimeout( app.requestTimeout )
				socket.on( 'timeout', function() {
					request.abort()
					doCallback( new Error('request timeout') )
				})
			}
		})

		// request error
		request.on( 'error', function( error ) {
			var err = new Error('request failed')
			err.error = error
			doCallback( err )
		})

		// request finished
		request.end()

	}
}
<|endoftext|>


<commit_before>var City = function() {
  var city = {};

  city.STREET_WIDTH = 2;
  city.STREET_DEPTH = 2;
  city.BLOCK_WIDTH = 4;
  city.BLOCK_DEPTH = 4;
  city.BLOCK_ROWS = 60;
  city.BLOCK_COLUMNS = 20;
  city.MIN_BUILDING_HEIGHT = 1.1;
  city.MAX_BUILDING_HEIGHT = 20;
  city.MAX_BUILDING_MATERIALS = 50;
  city.TOTAL_SCENE_WIDTH = (city.BLOCK_WIDTH * city.BLOCK_ROWS) + (city.STREET_WIDTH * (city.BLOCK_ROWS - 1));
  city.HALF_SCENE_WIDTH = city.TOTAL_SCENE_WIDTH / 2;
  city.TOTAL_SCENE_DEPTH = (city.BLOCK_DEPTH * city.BLOCK_COLUMNS) + (city.STREET_DEPTH * (city.BLOCK_COLUMNS - 1));
  city.HALF_SCENE_DEPTH = city.TOTAL_SCENE_DEPTH / 2;

  city.BLOCK_LAYOUTS = [
    [ { width:     1.0,  depth: 1.0,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.0 } ],

    [ { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.0 },
      { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.0 } ],

    [ { width:     1.0,  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ: -0.5 },
      { width:     1.0,  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.0 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX: -(2 / 3),  offsetFromBlockCenterZ:  0.5 },
      { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.5 },
      { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX:  (2 / 3),  offsetFromBlockCenterZ:  0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 } ],
  ];

  city.buildScene = function() {
    var scene = new THREE.Scene();

    scene.add(buildGroundGeometry());

    var buildingMaterials = buildMaterials();
    var buildingGeometries = buildEmptyGeometriesForBuildings();

    // Loop through the lower left corner of each block
    var x, z;
    for (x = -(city.HALF_SCENE_WIDTH); x < city.HALF_SCENE_WIDTH; x += city.BLOCK_WIDTH + city.STREET_WIDTH) {
      for (z = -(city.HALF_SCENE_DEPTH); z < city.HALF_SCENE_DEPTH; z += city.BLOCK_DEPTH + city.STREET_DEPTH) {
        generateBlock(x, z, buildingGeometries);
      }
    }

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      scene.add(new THREE.Mesh(buildingGeometries[i], buildingMaterials[i]));
    }

    return scene;
  };

  var buildGroundGeometry = function() {
    var groundMaterial = new THREE.MeshBasicMaterial({ color: 0xaaaaaa });
    var groundGeometry = new THREE.Mesh(new THREE.PlaneGeometry(city.TOTAL_SCENE_WIDTH * 25, city.TOTAL_SCENE_DEPTH * 25), groundMaterial);
    groundGeometry.rotation.x = -(Math.PI / 2);

    return groundGeometry;
  };

  var buildMaterials = function() {
    var buildingMaterials = [];

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      var r = Math.random();
      var g = r;
      var b = r;
      var buildingColor = new THREE.Color(0xffffff).setRGB(r, g, b);

      buildingMaterials.push(new THREE.MeshLambertMaterial({ color: buildingColor.getHex() }));
    }

    return buildingMaterials;
  };

  var buildEmptyGeometriesForBuildings = function() {
    var buildingGeometries = [];

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      buildingGeometries.push(new THREE.Geometry());
    }

    return buildingGeometries;
  };

  var generateBlock = function(x, z, buildingGeometries) {
    var i, lotLayout, buildingHeight, building;
    var blockLayout = city.BLOCK_LAYOUTS[Math.floor(Math.random() * city.BLOCK_LAYOUTS.length)];

    for (i = 0; i < blockLayout.length; i++) {
      lotLayout = blockLayout[i];
      buildingHeight = calculateBuildingHeight(x, z);
      building = generateBuilding(x, buildingHeight, z, lotLayout);

      var materialIndex = Math.floor(Math.random() * city.MAX_BUILDING_MATERIALS);
      buildingGeometries[materialIndex].merge(building.geometry, building.matrix);
    }
  };

  var calculateBuildingHeight = function(x, z) {
    var maxHeightX = city.MAX_BUILDING_HEIGHT * ((city.HALF_SCENE_WIDTH - Math.abs(x)) / city.HALF_SCENE_WIDTH);
    var maxHeightZ = city.MAX_BUILDING_HEIGHT * ((city.HALF_SCENE_DEPTH - Math.abs(z)) / city.HALF_SCENE_DEPTH);
    var maxHeight = Math.min(maxHeightX, maxHeightZ);
    //var buildingHeight = Math.floor((Math.random() * maxHeight - city.MIN_BUILDING_HEIGHT)) + city.MIN_BUILDING_HEIGHT;

    return maxHeight;
  };

  var generateBuilding = function(x, maxY, z, lotLayout) {
    var building = new THREE.Mesh(new THREE.BoxGeometry(lotLayout.width * city.BLOCK_WIDTH, 1, lotLayout.depth * city.BLOCK_WIDTH));

    building.position.x = x + city.BLOCK_WIDTH / 2 + ((city.BLOCK_WIDTH / 2) * lotLayout.offsetFromBlockCenterX);
    building.scale.y =  (Math.random() * maxY) + city.MIN_BUILDING_HEIGHT;
    building.position.y = building.scale.y / 2;
    building.position.z = z + city.BLOCK_DEPTH / 2 + ((city.BLOCK_DEPTH / 2) * lotLayout.offsetFromBlockCenterZ);
    building.updateMatrix();

    return building;
  };

  return city;
};
<commit_msg>Extracting constant<commit_after>var City = function() {
  var COLOR_GROUND = 0xaaaaaa;

  var city = {};

  city.STREET_WIDTH = 2;
  city.STREET_DEPTH = 2;
  city.BLOCK_WIDTH = 4;
  city.BLOCK_DEPTH = 4;
  city.BLOCK_ROWS = 60;
  city.BLOCK_COLUMNS = 20;
  city.MIN_BUILDING_HEIGHT = 1.1;
  city.MAX_BUILDING_HEIGHT = 20;
  city.MAX_BUILDING_MATERIALS = 50;
  city.TOTAL_SCENE_WIDTH = (city.BLOCK_WIDTH * city.BLOCK_ROWS) + (city.STREET_WIDTH * (city.BLOCK_ROWS - 1));
  city.HALF_SCENE_WIDTH = city.TOTAL_SCENE_WIDTH / 2;
  city.TOTAL_SCENE_DEPTH = (city.BLOCK_DEPTH * city.BLOCK_COLUMNS) + (city.STREET_DEPTH * (city.BLOCK_COLUMNS - 1));
  city.HALF_SCENE_DEPTH = city.TOTAL_SCENE_DEPTH / 2;

  city.BLOCK_LAYOUTS = [
    [ { width:     1.0,  depth: 1.0,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.0 } ],

    [ { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.0 },
      { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.0 } ],

    [ { width:     1.0,  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ: -0.5 },
      { width:     1.0,  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width:     0.5,  depth: 1.0,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.0 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ:  0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ:  0.5 } ],

    [ { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX: -(2 / 3),  offsetFromBlockCenterZ:  0.5 },
      { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX:      0.0,  offsetFromBlockCenterZ:  0.5 },
      { width: (1 / 3),  depth: 0.5,  offsetFromBlockCenterX:  (2 / 3),  offsetFromBlockCenterZ:  0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:     -0.5,  offsetFromBlockCenterZ: -0.5 },
      { width:     0.5,  depth: 0.5,  offsetFromBlockCenterX:      0.5,  offsetFromBlockCenterZ: -0.5 } ],
  ];

  city.buildScene = function() {
    var scene = new THREE.Scene();

    scene.add(buildGroundGeometry());

    var buildingMaterials = buildMaterials();
    var buildingGeometries = buildEmptyGeometriesForBuildings();

    // Loop through the lower left corner of each block
    var x, z;
    for (x = -(city.HALF_SCENE_WIDTH); x < city.HALF_SCENE_WIDTH; x += city.BLOCK_WIDTH + city.STREET_WIDTH) {
      for (z = -(city.HALF_SCENE_DEPTH); z < city.HALF_SCENE_DEPTH; z += city.BLOCK_DEPTH + city.STREET_DEPTH) {
        generateBlock(x, z, buildingGeometries);
      }
    }

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      scene.add(new THREE.Mesh(buildingGeometries[i], buildingMaterials[i]));
    }

    return scene;
  };

  var buildGroundGeometry = function() {
    var groundMaterial = new THREE.MeshBasicMaterial({ color: COLOR_GROUND, });
    var groundGeometry = new THREE.Mesh(new THREE.PlaneGeometry(city.TOTAL_SCENE_WIDTH * 25, city.TOTAL_SCENE_DEPTH * 25), groundMaterial);
    groundGeometry.rotation.x = -(Math.PI / 2);

    return groundGeometry;
  };

  var buildMaterials = function() {
    var buildingMaterials = [];

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      var r = Math.random();
      var g = r;
      var b = r;
      var buildingColor = new THREE.Color(0xffffff).setRGB(r, g, b);

      buildingMaterials.push(new THREE.MeshLambertMaterial({ color: buildingColor.getHex() }));
    }

    return buildingMaterials;
  };

  var buildEmptyGeometriesForBuildings = function() {
    var buildingGeometries = [];

    for (var i = 0; i < city.MAX_BUILDING_MATERIALS; i++) {
      buildingGeometries.push(new THREE.Geometry());
    }

    return buildingGeometries;
  };

  var generateBlock = function(x, z, buildingGeometries) {
    var i, lotLayout, buildingHeight, building;
    var blockLayout = city.BLOCK_LAYOUTS[Math.floor(Math.random() * city.BLOCK_LAYOUTS.length)];

    for (i = 0; i < blockLayout.length; i++) {
      lotLayout = blockLayout[i];
      buildingHeight = calculateBuildingHeight(x, z);
      building = generateBuilding(x, buildingHeight, z, lotLayout);

      var materialIndex = Math.floor(Math.random() * city.MAX_BUILDING_MATERIALS);
      buildingGeometries[materialIndex].merge(building.geometry, building.matrix);
    }
  };

  var calculateBuildingHeight = function(x, z) {
    var maxHeightX = city.MAX_BUILDING_HEIGHT * ((city.HALF_SCENE_WIDTH - Math.abs(x)) / city.HALF_SCENE_WIDTH);
    var maxHeightZ = city.MAX_BUILDING_HEIGHT * ((city.HALF_SCENE_DEPTH - Math.abs(z)) / city.HALF_SCENE_DEPTH);
    var maxHeight = Math.min(maxHeightX, maxHeightZ);
    //var buildingHeight = Math.floor((Math.random() * maxHeight - city.MIN_BUILDING_HEIGHT)) + city.MIN_BUILDING_HEIGHT;

    return maxHeight;
  };

  var generateBuilding = function(x, maxY, z, lotLayout) {
    var building = new THREE.Mesh(new THREE.BoxGeometry(lotLayout.width * city.BLOCK_WIDTH, 1, lotLayout.depth * city.BLOCK_WIDTH));

    building.position.x = x + city.BLOCK_WIDTH / 2 + ((city.BLOCK_WIDTH / 2) * lotLayout.offsetFromBlockCenterX);
    building.scale.y =  (Math.random() * maxY) + city.MIN_BUILDING_HEIGHT;
    building.position.y = building.scale.y / 2;
    building.position.z = z + city.BLOCK_DEPTH / 2 + ((city.BLOCK_DEPTH / 2) * lotLayout.offsetFromBlockCenterZ);
    building.updateMatrix();

    return building;
  };

  return city;
};
<|endoftext|>


<commit_before>exports.run = (client, msg, [type, name]) => {
  switch (type) {
    case "inhibitor": {
      const inhibitor = client.commandInhibitors.get(name);
      if (!inhibitor) return msg.channel.send(`- I cannot find the inhibitor: ${name}`, { code: "diff" });
      inhibitor.conf.enabled = true;
      return msg.channel.send("diff", `+ Successfully enabled inhibitor: ${name}`, { code: "diff" });
    }
    case "monitor": {
      const monitor = client.messageMonitors.get(name);
      if (!monitor) return msg.channel.send(`- I cannot find the monitor: ${name}`, { code: "diff" });
      monitor.conf.enabled = true;
      return msg.channel.send(`+ Successfully enabled monitor: ${name}`, { code: "diff" });
    }
    case "command": {
      const command = client.commands.get(name) || client.commands.get(client.aliases.has(name));
      if (!command) return msg.channel.send(`- I cannot find the command: ${name}`, { code: "diff" });
      command.conf.enabled = true;
      return msg.channel.send(`+ Successfully enabled command: ${name}`, { code: "diff" });
    }
    default:
      return msg.channel.send("This will never happen");
  }
};

exports.conf = {
  enabled: true,
  runIn: ["text", "dm", "group"],
  aliases: [],
  permLevel: 10,
  botPerms: [],
  requiredFuncs: [],
};

exports.help = {
  name: "enable",
  description: "Re-enables or temporarily enables a Inhibitor/Command/Monitor. Default state restored on reboot.",
  usage: "<inhibitor|monitor|command> <name:str>",
  usageDelim: " ",
};
<commit_msg>Delete enable.js<commit_after><|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor'
import { Ratings, RatingsTemplates, UserData, WalletImages } from '/imports/api/indexDB.js'
import { log } from '/server/main.js'

Meteor.methods({
	answerRating: function(ratingId, winner) {
		let rating = Ratings.findOne({_id:ratingId})

		if (rating.owner == this.userId) {
			var loser = rating.currency0Id;

			if(loser == winner) {
				loser = rating.currency1Id;
			}

			if(winner == "tie") {
				loser = "tie";
			}

			let question = RatingsTemplates.findOne({
				_id: rating.questionId
			})

			if (question.xors) {
				question.xors.forEach(i => {
					let q = RatingsTemplates.findOne({ _id: i })
					let r = Ratings.findOne({
						questionId: i,
						currency0Id: rating.currency0Id,
						currency1Id: rating.currency1Id
					}) || {} // get the rating on same currency pair

					let bo = true
					if ((!q.negative && question.negative) || (q.negative && !question.negative)) { // XOR
						bo = false
					}

					if (r.answered) {
						if (winner !== 'tie' && r.winner !== 'tie' && ((bo && (winner !== r.loser || loser !== r.winner)) || (!bo && (winner !== r.winner || loser !== r.loser)))) {
							throw new Meteor.Error('Error.', 'xor')
						}
					}
				})
			}

			Ratings.upsert({_id:ratingId}, {
				$set: {
					answered: true,
					winner: winner,
					loser: loser,
					answeredAt: new Date().getTime()
				}
			})

			Meteor.call('getWalletReward', Meteor.userId(), ratingId, (err, data) => {
				console.log(data)
				Ratings.update({
					_id: ratingId
				}, {
					$set: {
						reward: data // save the reward so we can remove it later on if needed
					}
				})

				creditUserWith(data, Meteor.userId(), 'answering a wallet question','answerQuestion')
			})
		}
    },

	addRatingQuestion: (question, catagory, negative, context, xors) => {
		console.log("addRatingQuestion has been called");
		if (!Meteor.userId()){
			throw new Meteor.Error('Error.', 'You need to be logged.')
		}

		let id = parseInt(`0x${CryptoJS.MD5(question).toString().slice(0,10)}`, 16).toString()

		let n = RatingsTemplates.insert({
            _id: id,
            question: question,
            catagory: catagory,
            createdBy: Meteor.userId(),
            createdAt: new Date().getTime(),
            negative: !!negative,
            context: context,
            xors: xors
        })

        // XOR is symmetrical
        xors.forEach(i => {
            RatingsTemplates.update({
                _id: i
            }, {
                $push: {
                    xors: n
                }
            })
        })
	},

	deleteQuestion: (questionId) => {
		let question = RatingsTemplates.findOne({ _id: questionId })
		// you can only delete a question if you're a moderator
		var moderatorValue = UserData.findOne({ _id: Meteor.userId() }, { fields: { moderator: true } }).moderator;

		if (moderatorValue === 1) {
			RatingsTemplates.remove({ _id: questionId })
		}
	},

	toggleContextQuestion: (questionId) => {
		let question = RatingsTemplates.findOne({ _id: questionId })

		// you can only change context of a question if you're a moderator
		var moderatorValue = UserData.findOne({ _id: Meteor.userId() }, { fields: { moderator: true } }).moderator;

		if (moderatorValue === 1) {
			RatingsTemplates.update({ _id: questionId }, {
				$set: { negative: !question.negative }
			})
		}
    },

	//this will populate the ratings database for this user with any new Currencies
    //they have added, or if an admin has added new questions for their existing currencies.
	populateRatings: function() {
		//fetch all the currencies this user uses:
		var images = WalletImages.find({createdBy: this.userId,allImagesUploaded:true}).fetch();
		var currencies = [];

		for (i in images) {
			currencies.push(images[i].currencyId);
		}
		var currencies = _.uniq(currencies);

		//fetch the questions that will be asked of the user
		var ratingTemplates = RatingsTemplates.find({
			$or: [{
				catagory: 'wallet'
			},
			{
				context: 'wallet'
			}]
		}).fetch();

		var userInt = parseInt("0x" + CryptoJS.MD5(this.userId).toString().slice(0,10), 16);

		//Cycle through all possible combinations of currencies that this user has a wallet for
		for (i = 0; i < currencies.length - 1; i++) {
			for (j = i + 1; j < currencies.length; j++) {
				//we don't want to generate duplicate currency pairs for the user, the fastest way to make sure
				//is to combine the currency pairs and user ID into a number so that no matter what way
				//you combine this the result will be the same, new additions can then be compared immediately
				//and be verified unique for this user.
				var dec_i = parseInt("0x" + CryptoJS.MD5(currencies[i]).toString().slice(0,10), 16);
				var dec_j = parseInt("0x" + CryptoJS.MD5(currencies[j]).toString().slice(0,10), 16);
				//add truncated MD5 of currencyId's and userId to prevent duplicates
				var _id = dec_i + dec_j + userInt;
				//add question truncated MD5 Int to the _id
				for (k in ratingTemplates) {
					console.log(currencies[i] + " " + currencies[j] + " " + ratingTemplates[k]._id)
					id = (_id + parseInt(ratingTemplates[k]._id, 10)).toString();
					console.log(id);
					try{
						Ratings.insert({
							_id: id,
							'owner': this.userId,
							'currency0Id': currencies[i],
							'currency1Id': currencies[j],
							'winner': null,
							'loser': null,
							'currency0approved': false,
							'currency1approved': false,
							'questionId': ratingTemplates[k]._id,
							'questionText': ratingTemplates[k].question,
							'createdAt': new Date().getTime(),
							'processedAt': null,
							'processed': false,
							'catagory': ratingTemplates[k].catagory,
							'context': ratingTemplates[k].context,
							'type': "wallet",
							'answeredAt': null,
							'answered': false
						})
					} catch(error) {
						log.error(`the combination of ${currencies[i]} and ${currencies[j]} exists in populateRatings!`, error)
					}
				}
			}
		}
    },
});
<commit_msg>problem: [/rankings] Exception while invoking method 'answerRating' #850<commit_after>import { Meteor } from 'meteor/meteor'
import { Ratings, RatingsTemplates, UserData, WalletImages } from '/imports/api/indexDB.js'
import { log } from '/server/main.js'

Meteor.methods({
	answerRating: function(ratingId, winner) {
		let rating = Ratings.findOne({_id:ratingId})

		if (rating.owner == this.userId) {
			var loser = rating.currency0Id;

			if(loser == winner) {
				loser = rating.currency1Id;
			}

			if(winner == "tie") {
				loser = "tie";
			}

			let question = RatingsTemplates.findOne({
				_id: rating.questionId
			})

			if (question !== undefined) {
				if (question.xors) {
					question.xors.forEach(i => {
						let q = RatingsTemplates.findOne({ _id: i })
						let r = Ratings.findOne({
							questionId: i,
							currency0Id: rating.currency0Id,
							currency1Id: rating.currency1Id
						}) || {} // get the rating on same currency pair

						let bo = true
						if ((!q.negative && question.negative) || (q.negative && !question.negative)) { // XOR
							bo = false
						}

						if (r.answered) {
							if (winner !== 'tie' && r.winner !== 'tie' && ((bo && (winner !== r.loser || loser !== r.winner)) || (!bo && (winner !== r.winner || loser !== r.loser)))) {
								throw new Meteor.Error('Error.', 'xor')
							}
						}
					})
				}
			}

			Ratings.upsert({_id:ratingId}, {
				$set: {
					answered: true,
					winner: winner,
					loser: loser,
					answeredAt: new Date().getTime()
				}
			})

			Meteor.call('getWalletReward', Meteor.userId(), ratingId, (err, data) => {
				console.log(data)
				Ratings.update({
					_id: ratingId
				}, {
					$set: {
						reward: data // save the reward so we can remove it later on if needed
					}
				})

				creditUserWith(data, Meteor.userId(), 'answering a wallet question','answerQuestion')
			})
		}
    },

	addRatingQuestion: (question, catagory, negative, context, xors) => {
		console.log("addRatingQuestion has been called");
		if (!Meteor.userId()){
			throw new Meteor.Error('Error.', 'You need to be logged.')
		}

		let id = parseInt(`0x${CryptoJS.MD5(question).toString().slice(0,10)}`, 16).toString()

		let n = RatingsTemplates.insert({
            _id: id,
            question: question,
            catagory: catagory,
            createdBy: Meteor.userId(),
            createdAt: new Date().getTime(),
            negative: !!negative,
            context: context,
            xors: xors
        })

        // XOR is symmetrical
        xors.forEach(i => {
            RatingsTemplates.update({
                _id: i
            }, {
                $push: {
                    xors: n
                }
            })
        })
	},

	deleteQuestion: (questionId) => {
		let question = RatingsTemplates.findOne({ _id: questionId })
		// you can only delete a question if you're a moderator
		var moderatorValue = UserData.findOne({ _id: Meteor.userId() }, { fields: { moderator: true } }).moderator;

		if (moderatorValue === 1) {
			RatingsTemplates.remove({ _id: questionId })
		}
	},

	toggleContextQuestion: (questionId) => {
		let question = RatingsTemplates.findOne({ _id: questionId })

		// you can only change context of a question if you're a moderator
		var moderatorValue = UserData.findOne({ _id: Meteor.userId() }, { fields: { moderator: true } }).moderator;

		if (moderatorValue === 1) {
			RatingsTemplates.update({ _id: questionId }, {
				$set: { negative: !question.negative }
			})
		}
    },

	//this will populate the ratings database for this user with any new Currencies
    //they have added, or if an admin has added new questions for their existing currencies.
	populateRatings: function() {
		//fetch all the currencies this user uses:
		var images = WalletImages.find({createdBy: this.userId,allImagesUploaded:true}).fetch();
		var currencies = [];

		for (i in images) {
			currencies.push(images[i].currencyId);
		}
		var currencies = _.uniq(currencies);

		//fetch the questions that will be asked of the user
		var ratingTemplates = RatingsTemplates.find({
			$or: [{
				catagory: 'wallet'
			},
			{
				context: 'wallet'
			}]
		}).fetch();

		var userInt = parseInt("0x" + CryptoJS.MD5(this.userId).toString().slice(0,10), 16);

		//Cycle through all possible combinations of currencies that this user has a wallet for
		for (i = 0; i < currencies.length - 1; i++) {
			for (j = i + 1; j < currencies.length; j++) {
				//we don't want to generate duplicate currency pairs for the user, the fastest way to make sure
				//is to combine the currency pairs and user ID into a number so that no matter what way
				//you combine this the result will be the same, new additions can then be compared immediately
				//and be verified unique for this user.
				var dec_i = parseInt("0x" + CryptoJS.MD5(currencies[i]).toString().slice(0,10), 16);
				var dec_j = parseInt("0x" + CryptoJS.MD5(currencies[j]).toString().slice(0,10), 16);
				//add truncated MD5 of currencyId's and userId to prevent duplicates
				var _id = dec_i + dec_j + userInt;
				//add question truncated MD5 Int to the _id
				for (k in ratingTemplates) {
					console.log(currencies[i] + " " + currencies[j] + " " + ratingTemplates[k]._id)
					id = (_id + parseInt(ratingTemplates[k]._id, 10)).toString();
					console.log(id);
					try{
						Ratings.insert({
							_id: id,
							'owner': this.userId,
							'currency0Id': currencies[i],
							'currency1Id': currencies[j],
							'winner': null,
							'loser': null,
							'currency0approved': false,
							'currency1approved': false,
							'questionId': ratingTemplates[k]._id,
							'questionText': ratingTemplates[k].question,
							'createdAt': new Date().getTime(),
							'processedAt': null,
							'processed': false,
							'catagory': ratingTemplates[k].catagory,
							'context': ratingTemplates[k].context,
							'type': "wallet",
							'answeredAt': null,
							'answered': false
						})
					} catch(error) {
						log.error(`the combination of ${currencies[i]} and ${currencies[j]} exists in populateRatings!`, error)
					}
				}
			}
		}
    },
});
<|endoftext|>


<commit_before>const https = require('https');
const exec = require('child_process').exec;

// TODO: switch to develop branch
const mappingsSrc = 'https://raw.githubusercontent.com/service-mocker/service-mocker/develop/ts-compat/versions.json';

const MOCKER_MATCHED = 1 << 0;                   // 01
const TS_MATCHED = 1 << 1;                       // 10
const ALL_MATCHED = MOCKER_MATCHED | TS_MATCHED; // 11

function installDeps() {
  const deps = Array.prototype.slice.call(arguments);
  const missed = [];

  deps.forEach((pkg) => {
    try {
      require.resolve(pkg);
    } catch (e) {
      missed.push(pkg);
    }
  });

  if (!missed.length) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    exec(`npm install ${missed.join(' ')}`, (err, stdout, stderr) => {
      if (err || stderr) {
        reject(err || stderr);
      } else {
        resolve(stdout);
      }
    });
  });
}

function fetchRemote() {
  return new Promise((resolve, reject) => {
    https.get(mappingsSrc, (res) => {
      let rawData = '';

      res.on('data', (chunk) => {
        rawData += chunk;
      });

      res.on('end', () => {
        try {
          resolve(JSON.parse(rawData));
        } catch (e) {
          reject(e);
        }
      });
    }).on('error', reject);
  });
}

function compareVer(versions) {
  const mockerVersion = require('../package.json').version;
  const tsVersion = require('typescript').version;

  return fetchRemote()
    .then((data) => {
      const semver = require('semver');

      let error = null;

      const tsVersionFound = data.mappings.some((mapping) => {
        let state = 0;

        if (semver.satisfies(mockerVersion, mapping['service-mocker'])) {
          state |= MOCKER_MATCHED;
        }

        if (semver.satisfies(tsVersion, mapping['typescript'])) {
          state |= TS_MATCHED;
        }

        switch (state) {
          case ALL_MATCHED:
            return true;

          case TS_MATCHED:
            error = new Error(`service-mocker@${mockerVersion} is not compatible with typescript@${tsVersion}, please use service-mocker@${mapping['service-mocker']}`);
            return true;

          case MOCKER_MATCHED:
            return false; // next
        }
      });

      if (!tsVersionFound) {
        error = new Error(`service-mocker dose not support typescript@${tsVersion} yet, please file an issue on https://github.com/service-mocker/service-mocker/issues.`);
      }

      if (error !== null) {
        console.info('Compatibility List:', JSON.stringify(data.mappings, null, 2));
        throw error;
      }
    });
}

try {
  // check ts existence
  require.resolve('typescript');

  installDeps('semver')
    .then(compareVer)
    .catch((error) => {
      console.error(`\x1b[31m${error}\x1b[0m`);
      process.exit(1);
    });
} catch (e) {}
<commit_msg>chore(ts-compat): remove useless comment [skip ci]<commit_after>const https = require('https');
const exec = require('child_process').exec;

const mappingsSrc = 'https://raw.githubusercontent.com/service-mocker/service-mocker/develop/ts-compat/versions.json';

const MOCKER_MATCHED = 1 << 0;                   // 01
const TS_MATCHED = 1 << 1;                       // 10
const ALL_MATCHED = MOCKER_MATCHED | TS_MATCHED; // 11

function installDeps() {
  const deps = Array.prototype.slice.call(arguments);
  const missed = [];

  deps.forEach((pkg) => {
    try {
      require.resolve(pkg);
    } catch (e) {
      missed.push(pkg);
    }
  });

  if (!missed.length) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    exec(`npm install ${missed.join(' ')}`, (err, stdout, stderr) => {
      if (err || stderr) {
        reject(err || stderr);
      } else {
        resolve(stdout);
      }
    });
  });
}

function fetchRemote() {
  return new Promise((resolve, reject) => {
    https.get(mappingsSrc, (res) => {
      let rawData = '';

      res.on('data', (chunk) => {
        rawData += chunk;
      });

      res.on('end', () => {
        try {
          resolve(JSON.parse(rawData));
        } catch (e) {
          reject(e);
        }
      });
    }).on('error', reject);
  });
}

function compareVer(versions) {
  const mockerVersion = require('../package.json').version;
  const tsVersion = require('typescript').version;

  return fetchRemote()
    .then((data) => {
      const semver = require('semver');

      let error = null;

      const tsVersionFound = data.mappings.some((mapping) => {
        let state = 0;

        if (semver.satisfies(mockerVersion, mapping['service-mocker'])) {
          state |= MOCKER_MATCHED;
        }

        if (semver.satisfies(tsVersion, mapping['typescript'])) {
          state |= TS_MATCHED;
        }

        switch (state) {
          case ALL_MATCHED:
            return true;

          case TS_MATCHED:
            error = new Error(`service-mocker@${mockerVersion} is not compatible with typescript@${tsVersion}, please use service-mocker@${mapping['service-mocker']}`);
            return true;

          case MOCKER_MATCHED:
            return false; // next
        }
      });

      if (!tsVersionFound) {
        error = new Error(`service-mocker dose not support typescript@${tsVersion} yet, please file an issue on https://github.com/service-mocker/service-mocker/issues.`);
      }

      if (error !== null) {
        console.info('Compatibility List:', JSON.stringify(data.mappings, null, 2));
        throw error;
      }
    });
}

try {
  // check ts existence
  require.resolve('typescript');

  installDeps('semver')
    .then(compareVer)
    .catch((error) => {
      console.error(`\x1b[31m${error}\x1b[0m`);
      process.exit(1);
    });
} catch (e) {}
<|endoftext|>


<commit_before>'use strict'

let Task    = require('../models/task'),
    rsvp    = require('rsvp'),
    chalk   = require('chalk'),
    express = require('express'),
    livereload = require('express-livereload'),
    Watcher = require('../models/watcher'),
    Build   = require('../tasks/build');

class Server extends Task {

    run(args) {
        return new rsvp.Promise((resolve, reject) => {

            let port = args[0];
            let watchDirectory = args[1];
            let app;
            let builder = new Build('development', null);
            builder
            .run([])
            .then((result) => {
                let directory = result.directory;
                console.log(chalk.green(`Starting server on port ${port} for directory ${directory}`));

                app = express();
                app.use(express.static(directory));

                livereload(app, {'watchDir': directory});

                let watcher = new Watcher(watchDirectory, () => {
                    new Build('development', directory)
                    .run()
                });

                app.listen(port);
            });
        });
    }

}

module.exports = Server;
<commit_msg>Catch build errors while serving.<commit_after>'use strict'

let Task    = require('../models/task'),
    rsvp    = require('rsvp'),
    chalk   = require('chalk'),
    express = require('express'),
    livereload = require('express-livereload'),
    Watcher = require('../models/watcher'),
    Build   = require('../tasks/build');

class Server extends Task {

    run(args) {
        return new rsvp.Promise((resolve, reject) => {

            let port = args[0];
            let watchDirectory = args[1];
            let app;
            let builder = new Build('development', null);
            builder
            .run([])
            .then((result) => {
                let directory = result.directory;
                console.log(chalk.green(`Starting server on port ${port} for directory ${directory}`));

                app = express();
                app.use(express.static(directory));

                livereload(app, {'watchDir': directory});

                let watcher = new Watcher(watchDirectory, () => {
                    new Build('development', directory)
                    .run()
                    .catch(e => console.log(chalk.red(e.stack)));
                });

                app.listen(port);
            }).catch(e => console.log(chalk.red(e.stack)));
        });
    }

}

module.exports = Server;
<|endoftext|>


<commit_before>import Command from './command/Command.js';
import Converter from './converter/Converter.js';
import EditorConfig from './config/EditorConfig.js';
import Observer from './observer/Observer.js';
import Tag from './tag/Tag.js';
import TextCommand from './command/TextCommand.js';
import configCommand from '../cfg/command.js';
import configConverter from '../cfg/converter.js';
import configObserver from '../cfg/observer.js';
import configTag from '../cfg/tag.js';

/**
 * Editor
 */
export default class Editor {
    /**
     * Creates a new instance of editor with given configuration
     *
     * @param {HTMLElement} orig
     * @param {Object} config
     */
    constructor(orig, config = {}) {
        if (!(orig instanceof HTMLElement)) {
            throw 'Invalid element';
        }

        const element = orig.ownerDocument.createElement('div');
        const content = orig.ownerDocument.createElement('div');
        const toolbarWidget = orig.ownerDocument.createElement('div');
        const toolbarEditable = orig.ownerDocument.createElement('div');

        element.classList.add('editor');
        content.classList.add('editor-content');
        toolbarWidget.classList.add('editor-toolbar', 'editor-toolbar-widget');
        toolbarEditable.classList.add('editor-toolbar', 'editor-toolbar-editable');

        orig.hidden = true;
        orig.insertAdjacentElement('afterend', element);
        element.appendChild(toolbarWidget);
        element.appendChild(content);
        element.appendChild(toolbarEditable);

        /**
         * Corresponding DOM element of the source
         *
         * @type {HTMLElement}
         * @readonly
         */
        this.orig = orig;

        /**
         * Correspondig DOM Document
         *
         * @type {Document}
         * @readonly
         */
        this.document = orig.ownerDocument;

        /**
         * Corresponding Window object
         *
         * @type {Window}
         * @readonly
         */
        this.window = this.document.defaultView;

        /**
         * Configuration
         *
         * @type {EditorConfig}
         * @readonly
         */
        this.config = new EditorConfig(config);

        /**
         * Corresponding DOM element of the editor
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.element = element;

        /**
         * Corresponding DOM element of the editor content
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.content = content;

        /**
         * Corresponding DOM element of the widget toolbar
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.toolbarWidget = toolbarWidget;

        /**
         * Corresponding DOM element of the editable toolbar
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.toolbarEditable = toolbarEditable;

        /**
         * Tags
         *
         * @type {Map<String, Tag>}
         * @readonly
         */
        this.tags = this.createTags(configTag);

        /**
         * Element converters
         *
         * @type {Map<String, Converter>}
         * @readonly
         */
        this.converters = this.createConverters(configConverter);

        /**
         * Commands
         *
         * @type {Map<String, Command>}
         */
        this.commands = this.createCommands(configCommand);

        /**
         * Observers
         *
         * @type {Observer[]}
         */
        this.observers = this.createObservers(configObserver);
    }

    /**
     * Creates tags map
     *
     * @param {Object[]} config
     *
     * @return {Map<String, Tag>}
     */
    createTags(config) {
        const map = new Map();

        config.forEach(item => {
            const tag = new Tag(item);
            return map.set(tag.name, tag);
        });

        return map;
    }

    /**
     * Creates converters map
     *
     * @param {Object.<String, Converter>} config
     *
     * @return {Map<String, Converter>}
     */
    createConverters(config) {
        const map = new Map();

        Object.getOwnPropertyNames(config).forEach(key => {
            if (!(config[key] instanceof Converter)) {
                throw 'Invalid converter';
            }

            map.set(key, config[key]);
        });

        return map;
    }

    /**
     * Creates observers
     *
     * @param {Observer[]} config
     *
     * @return {Observer[]}
     */
    createObservers(config) {
        return config.map(item => {
            item = new item(this);

            if (!(item instanceof Observer)) {
                throw 'Invalid observer';
            }

            return item;
        });
    }

    /**
     * Creates commands map
     *
     * @param {Object.<String, Function>} config
     *
     * @return {Map<String, Command>}
     */
    createCommands(config) {
        const map = new Map();

        Object.getOwnPropertyNames(config).forEach(key => {
            let command;

            if (typeof config[key] !== 'function' || !(command = config[key](this)) || !(command instanceof Command)) {
                throw 'Invalid command';
            }

            return map.set(key, command);
        });

        return map;
    }

    /**
     * Init editor
     */
    init() {
        this.observers.forEach(item => this.register(ev => item.observe(ev)));
        this.initContent();
        this.initToolbar();
    }

    /**
     * Init content
     */
    initContent() {
        if (this.orig instanceof HTMLTextAreaElement) {
            this.content.innerHTML = this.orig.value.replace('/ /g', ' ');
            this.orig.form.addEventListener('submit', () => this.save());
        } else {
            this.content.innerHTML = this.orig.innerHTML;
        }

        this.filter(this.content);
    }

    /**
     * Init Toolbar
     */
    initToolbar() {
        this.content.addEventListener('selectstart', () => {
            const active = this.document.activeElement;

            if (active.isContentEditable && this.allowedGroup('text', active.tagName)) {
                this.toolbarEditable.classList.add('editor-toolbar-active');
                this.toolbarEditable.style.top = (active.offsetTop + active.offsetParent.offsetTop - this.toolbarEditable.clientHeight) + 'px';
            }
        });
        this.document.addEventListener('selectionchange', () => {
            const active = this.document.activeElement;

            if (this.window.getSelection().isCollapsed || !active.isContentEditable || !this.content.contains(active)) {
                this.toolbarEditable.classList.remove('editor-toolbar-active');
                this.toolbarEditable.removeAttribute('style');
            }
        });

        for (let cmd of this.commands.entries()) {
            const item = this.document.createElement('button');
            item.setAttribute('type', 'button');
            item.setAttribute('data-cmd', cmd[0]);
            item.setAttribute('title', cmd[0]);
            item.innerText = cmd[0];
            item.addEventListener('click', () => {
                if (!this.window.getSelection().containsNode(this.content, true)) {
                    this.content.focus();
                }

                cmd[1].execute();
            });

            if (cmd[1] instanceof TextCommand) {
                this.toolbarEditable.appendChild(item);
            } else {
                this.toolbarWidget.appendChild(item);
            }
        }
    }

    /**
     * Short-cut method to register a mutation observer
     *
     * @param {Function} call
     * @param {Object} config
     */
    register(call, config = {childList: true, subtree: true}) {
        if (typeof call !== 'function') {
            throw 'Invalid observer';
        }

        const mutation = new MutationObserver(call);
        mutation.observe(this.content, config);
    }

    /**
     * Execute command
     *
     * @param {String} name
     */
    execute(name) {
        const command = this.commands.get(name);

        if (!command) {
            throw 'Invalid command';
        }

        command.execute();
    }

    /**
     * Returns editor element's innerHTML
     *
     * @return {String}
     */
    getData() {
        const content = this.content.cloneNode(true);
        this.filter(content, true);

        return content.innerHTML;
    }

    /**
     * Saves editor data to source element
     */
    save() {
        if (this.orig instanceof HTMLTextAreaElement) {
            this.orig.value = this.getData();
        } else {
            this.orig.innerHTML = this.getData();
        }
    }

    /**
     * Inserts an element at the editor element or optionally after given reference element
     *
     * @param {HTMLElement} element
     * @param {?HTMLElement} ref
     */
    insert(element, ref = null) {
        if (!(element instanceof HTMLElement) || ref && !(ref instanceof HTMLElement)) {
            throw 'Invalid HTML element';
        }

        const parent = ref ? ref.parentElement : this.content;
        const parentName = this.getTagName(parent);

        if (!this.content.contains(parent) || !this.allowed(element.tagName, parentName)) {
            throw 'Element is not allowed here';
        }

        parent.insertBefore(element, ref ? ref.nextElementSibling : null)
    }

    /**
     * Insert text
     *
     * @param {String} text
     */
    insertText(text) {
        this.document.execCommand('inserttext', false, text);
    }

    /**
     * Adds or removes formatting to/from selected text
     *
     * @param {HTMLElement} element
     */
    formatText(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'Invalid HTML element';
        }

        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const ancEdit = this.getSelectedEditable();

        if (sel.isCollapsed || !sel.toString().trim() || !(anc instanceof HTMLElement) || !ancEdit) {
            return;
        }

        const range = sel.getRangeAt(0);
        const tag = this.getTag(anc.tagName);
        const parent = !tag || tag.group === 'text' ? anc.parentElement : anc;

        if (range.startContainer instanceof Text && !range.startContainer.parentElement.isSameNode(parent)) {
            range.setStartBefore(range.startContainer.parentElement);
        }

        if (range.endContainer instanceof Text && !range.endContainer.parentElement.isSameNode(parent)) {
            range.setEndAfter(range.endContainer.parentElement);
        }

        const selText = range.toString();
        const selNodes = range.cloneContents().childNodes;
        let same = Array.from(selNodes).every(item => {
            return item instanceof Text && !item.nodeValue.trim() || item instanceof HTMLElement && item.tagName === element.tagName;
        });

        range.deleteContents();

        if (parent.isContentEditable && this.allowed(element.tagName, parent.tagName) && selText.trim() && (!tag || !same)) {
            element.innerText = selText;
            range.insertNode(element);
        } else {
            range.insertNode(this.document.createTextNode(selText));
        }

        parent.normalize();
    }

    /**
     * Returns current selected element or null
     *
     * @return {?HTMLElement}
     */
    getSelectedElement() {
        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const foc = sel.focusNode instanceof Text ? sel.focusNode.parentElement : sel.focusNode;

        return anc instanceof HTMLElement && foc instanceof HTMLElement && anc.isSameNode(foc) && this.content.contains(anc) ? anc : null;
    }

    /**
     * Returns current selected contenteditable or null
     *
     * @return {?HTMLElement}
     */
    getSelectedEditable() {
        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const foc = sel.focusNode instanceof Text ? sel.focusNode.parentElement : sel.focusNode;

        if (anc instanceof HTMLElement && foc instanceof HTMLElement) {
            const ancEdit = anc.closest('[contenteditable=true]');
            const focEdit = foc.closest('[contenteditable=true]');

            if (ancEdit instanceof HTMLElement && focEdit instanceof HTMLElement && ancEdit.isSameNode(focEdit) && this.content.contains(ancEdit)) {
                return ancEdit;
            }
        }

        return null;
    }

    /**
     * Returns current selected widget or null
     *
     * @return {?HTMLElement}
     */
    getSelectedWidget() {
        const el = this.getSelectedElement();

        return el ? el.closest('div.editor-content > *') : null;
    }

    /**
     * Filters element
     *
     * @param {HTMLElement} parent
     * @param {Boolean} forceRoot
     */
    filter(parent, forceRoot = false) {
        if (!(parent instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        const isRoot = forceRoot || this.content.isSameNode(parent);
        const parentName = forceRoot ? 'root' : this.getTagName(parent);
        let br;

        parent.normalize();
        Array.from(parent.childNodes).forEach(node => {
            if (node instanceof HTMLElement) {
                node = this.convert(node);
                const name = node.tagName;
                const tag = this.getTag(name);
                const text = node.innerText.trim();

                if (!tag) {
                    parent.removeChild(node);
                } else if (this.allowed(name, parentName) || isRoot && tag.group === 'text') {
                    Array.from(node.attributes).forEach(item => {
                        if (!tag.attributes.includes(item.name)) {
                            node.removeAttribute(item.name);
                        }
                    });

                    if (node.hasChildNodes()) {
                        this.filter(node);
                    }

                    if (!node.hasChildNodes() && !tag.empty && !(node instanceof HTMLTableCellElement)) {
                        parent.removeChild(node);
                    } else if (isRoot && tag.group === 'text') {
                        const p = this.document.createElement('p');
                        p.innerHTML = node.outerHTML;
                        parent.replaceChild(p, node);
                    }
                } else if (isRoot && text) {
                    const p = this.document.createElement('p');
                    p.innerText = text;
                    parent.replaceChild(p, node);
                } else if (text) {
                    parent.replaceChild(this.document.createTextNode(text), node);
                } else {
                    parent.removeChild(node);
                }
            } else if (node instanceof Text) {
                if (isRoot && node.nodeValue.trim()) {
                    const p = this.document.createElement('p');
                    p.innerText = node.nodeValue.trim();
                    parent.replaceChild(p, node);
                } else if (isRoot) {
                    parent.removeChild(node);
                }
            } else {
                parent.removeChild(node);
            }
        });

        // Trim br elements
        while ((br = parent.firstChild) && br instanceof HTMLBRElement || (br = parent.lastChild) && br instanceof HTMLBRElement) {
            parent.removeChild(br);
        }

        // Filter our figure elements with figcaption elements as only child
        parent.querySelectorAll('figure > figcaption:only-child').forEach(node => node.parentElement.removeChild(node));

        // Filter empty tables and table parts
        parent.querySelectorAll('tbody').forEach(node => !node.querySelector('th:not(:empty), td:not(:empty)') && node.parentElement.parentElement.removeChild(node.parentElement));
        ['thead', 'tfoot', 'tr'].forEach(
            item => parent.querySelectorAll(item).forEach(node => !node.querySelector('th:not(:empty), td:not(:empty)') && node.parentElement.removeChild(node))
        );
    }

    /**
     * Returns tag name from element considering execption for root element
     *
     * @param {HTMLElement} element
     *
     * @return {String}
     */
    getTagName(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        return this.content.isSameNode(element) ? 'root' : element.tagName;
    }

    /**
     * Returns tag configuration vor given tag name
     *
     * @param {String} name
     *
     * @return {?Tag}
     */
    getTag(name) {
        return this.tags.get(name.toLowerCase()) || null;
    }

    /**
     * Checks if given element is allowed inside given parent element
     *
     * @param {String} name
     * @param {String} parentName
     *
     * @return {Boolean}
     */
    allowed(name, parentName) {
        const tag = this.getTag(name);
        const parentTag = this.getTag(parentName);

        return tag && parentTag && parentTag.children.includes(tag.group);
    }

    /**
     * Checks if given group is allowed inside given parent element
     *
     * @param {String} group
     * @param {String} parentName
     *
     * @return {Boolean}
     */
    allowedGroup(group, parentName) {
        const parentTag = this.getTag(parentName);

        return parentTag && parentTag.children.includes(group);
    }

    /**
     * Converts element
     *
     * @param {HTMLElement} element
     *
     * @return {Node}
     */
    convert(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        const converter = this.converters.get(element.tagName.toLowerCase());

        if (!converter) {
            return element;
        }

        const newNode = converter.convert(element);
        element.parentElement.replaceChild(newNode, element);

        return newNode;
    }

    /**
     * Factory method to create a new instance of editor with given configuration
     *
     * @param {HTMLElement} element
     * @param {Object} config
     *
     * @return {Editor}
     */
    static create(element, config = {}) {
        const editor = new Editor(element, config);
        editor.init();

        return editor;
    }
}
<commit_msg>Minimal change<commit_after>import Command from './command/Command.js';
import Converter from './converter/Converter.js';
import EditorConfig from './config/EditorConfig.js';
import Observer from './observer/Observer.js';
import Tag from './tag/Tag.js';
import TextCommand from './command/TextCommand.js';
import configCommand from '../cfg/command.js';
import configConverter from '../cfg/converter.js';
import configObserver from '../cfg/observer.js';
import configTag from '../cfg/tag.js';

/**
 * Editor
 */
export default class Editor {
    /**
     * Creates a new instance of editor with given configuration
     *
     * @param {HTMLElement} orig
     * @param {Object} config
     */
    constructor(orig, config = {}) {
        if (!(orig instanceof HTMLElement)) {
            throw 'Invalid element';
        }

        const element = orig.ownerDocument.createElement('div');
        const content = orig.ownerDocument.createElement('div');
        const toolbarWidget = orig.ownerDocument.createElement('div');
        const toolbarEditable = orig.ownerDocument.createElement('div');

        element.classList.add('editor');
        content.classList.add('editor-content');
        toolbarWidget.classList.add('editor-toolbar', 'editor-toolbar-widget');
        toolbarEditable.classList.add('editor-toolbar', 'editor-toolbar-editable');

        orig.hidden = true;
        orig.insertAdjacentElement('afterend', element);
        element.appendChild(toolbarWidget);
        element.appendChild(content);
        element.appendChild(toolbarEditable);

        /**
         * Corresponding DOM element of the source
         *
         * @type {HTMLElement}
         * @readonly
         */
        this.orig = orig;

        /**
         * Correspondig DOM Document
         *
         * @type {Document}
         * @readonly
         */
        this.document = orig.ownerDocument;

        /**
         * Corresponding Window object
         *
         * @type {Window}
         * @readonly
         */
        this.window = this.document.defaultView;

        /**
         * Configuration
         *
         * @type {EditorConfig}
         * @readonly
         */
        this.config = new EditorConfig(config);

        /**
         * Corresponding DOM element of the editor
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.element = element;

        /**
         * Corresponding DOM element of the editor content
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.content = content;

        /**
         * Corresponding DOM element of the widget toolbar
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.toolbarWidget = toolbarWidget;

        /**
         * Corresponding DOM element of the editable toolbar
         *
         * @type {HTMLDivElement}
         * @readonly
         */
        this.toolbarEditable = toolbarEditable;

        /**
         * Tags
         *
         * @type {Map<String, Tag>}
         * @readonly
         */
        this.tags = this.createTags(configTag);

        /**
         * Element converters
         *
         * @type {Map<String, Converter>}
         * @readonly
         */
        this.converters = this.createConverters(configConverter);

        /**
         * Commands
         *
         * @type {Map<String, Command>}
         */
        this.commands = this.createCommands(configCommand);

        /**
         * Observers
         *
         * @type {Observer[]}
         */
        this.observers = this.createObservers(configObserver);
    }

    /**
     * Creates tags map
     *
     * @param {Object[]} config
     *
     * @return {Map<String, Tag>}
     */
    createTags(config) {
        const map = new Map();

        config.forEach(item => {
            const tag = new Tag(item);
            return map.set(tag.name, tag);
        });

        return map;
    }

    /**
     * Creates converters map
     *
     * @param {Object.<String, Converter>} config
     *
     * @return {Map<String, Converter>}
     */
    createConverters(config) {
        const map = new Map();

        Object.getOwnPropertyNames(config).forEach(key => {
            if (!(config[key] instanceof Converter)) {
                throw 'Invalid converter';
            }

            map.set(key, config[key]);
        });

        return map;
    }

    /**
     * Creates observers
     *
     * @param {Observer[]} config
     *
     * @return {Observer[]}
     */
    createObservers(config) {
        return config.map(item => {
            item = new item(this);

            if (!(item instanceof Observer)) {
                throw 'Invalid observer';
            }

            return item;
        });
    }

    /**
     * Creates commands map
     *
     * @param {Object.<String, Function>} config
     *
     * @return {Map<String, Command>}
     */
    createCommands(config) {
        const map = new Map();

        Object.getOwnPropertyNames(config).forEach(key => {
            let command;

            if (typeof config[key] !== 'function' || !(command = config[key](this)) || !(command instanceof Command)) {
                throw 'Invalid command';
            }

            return map.set(key, command);
        });

        return map;
    }

    /**
     * Init editor
     */
    init() {
        this.observers.forEach(item => this.register(ev => item.observe(ev)));
        this.initContent();
        this.initToolbar();
    }

    /**
     * Init content
     */
    initContent() {
        if (this.orig instanceof HTMLTextAreaElement) {
            this.content.innerHTML = this.orig.value.replace('/ /g', ' ');
            this.orig.form.addEventListener('submit', () => this.save());
        } else {
            this.content.innerHTML = this.orig.innerHTML;
        }

        this.filter(this.content);
    }

    /**
     * Init Toolbar
     */
    initToolbar() {
        this.content.addEventListener('selectstart', () => {
            const active = this.document.activeElement;

            if (active.isContentEditable && this.allowedGroup('text', active.tagName)) {
                this.toolbarEditable.classList.add('editor-toolbar-active');
                this.toolbarEditable.style.top = (active.offsetTop + active.offsetParent.offsetTop - this.toolbarEditable.clientHeight) + 'px';
            }
        });
        this.document.addEventListener('selectionchange', () => {
            const active = this.document.activeElement;

            if (this.window.getSelection().isCollapsed || !active.isContentEditable || !this.content.contains(active)) {
                this.toolbarEditable.classList.remove('editor-toolbar-active');
                this.toolbarEditable.removeAttribute('style');
            }
        });

        for (let cmd of this.commands.entries()) {
            const item = this.document.createElement('button');
            item.setAttribute('type', 'button');
            item.setAttribute('data-cmd', cmd[0]);
            item.setAttribute('title', cmd[0]);
            item.innerText = cmd[0];
            item.addEventListener('click', () => {
                if (!this.window.getSelection().containsNode(this.content, true)) {
                    this.content.focus();
                }

                cmd[1].execute();
            });

            if (cmd[1] instanceof TextCommand) {
                this.toolbarEditable.appendChild(item);
            } else {
                this.toolbarWidget.appendChild(item);
            }
        }
    }

    /**
     * Short-cut method to register a mutation observer
     *
     * @param {Function} call
     * @param {Object} config
     */
    register(call, config = {childList: true, subtree: true}) {
        if (typeof call !== 'function') {
            throw 'Invalid observer';
        }

        const mutation = new MutationObserver(call);
        mutation.observe(this.content, config);
    }

    /**
     * Execute command
     *
     * @param {String} name
     */
    execute(name) {
        const command = this.commands.get(name);

        if (!command) {
            throw 'Invalid command';
        }

        command.execute();
    }

    /**
     * Returns editor element's innerHTML
     *
     * @return {String}
     */
    getData() {
        const content = this.content.cloneNode(true);
        this.filter(content, true);

        return content.innerHTML;
    }

    /**
     * Saves editor data to source element
     */
    save() {
        if (this.orig instanceof HTMLTextAreaElement) {
            this.orig.value = this.getData();
        } else {
            this.orig.innerHTML = this.getData();
        }
    }

    /**
     * Inserts an element at the editor element or optionally after given reference element
     *
     * @param {HTMLElement} element
     * @param {?HTMLElement} ref
     */
    insert(element, ref = null) {
        if (!(element instanceof HTMLElement) || ref && !(ref instanceof HTMLElement)) {
            throw 'Invalid HTML element';
        }

        const parent = ref ? ref.parentElement : this.content;
        const parentName = this.getTagName(parent);

        if (!this.content.contains(parent) || !this.allowed(element.tagName, parentName)) {
            throw 'Element is not allowed here';
        }

        parent.insertBefore(element, ref ? ref.nextElementSibling : null)
    }

    /**
     * Insert text
     *
     * @param {String} text
     */
    insertText(text) {
        this.document.execCommand('inserttext', false, text);
    }

    /**
     * Adds or removes formatting to/from selected text
     *
     * @param {HTMLElement} element
     */
    formatText(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'Invalid HTML element';
        }

        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const ancEdit = this.getSelectedEditable();

        if (sel.isCollapsed || !sel.toString().trim() || !(anc instanceof HTMLElement) || !ancEdit) {
            return;
        }

        const range = sel.getRangeAt(0);
        const tag = this.getTag(anc.tagName);
        const parent = !tag || tag.group === 'text' ? anc.parentElement : anc;

        if (range.startContainer instanceof Text && !range.startContainer.parentElement.isSameNode(parent)) {
            range.setStartBefore(range.startContainer.parentElement);
        }

        if (range.endContainer instanceof Text && !range.endContainer.parentElement.isSameNode(parent)) {
            range.setEndAfter(range.endContainer.parentElement);
        }

        const selText = range.toString();
        const selNodes = range.cloneContents().childNodes;
        let same = Array.from(selNodes).every(item => {
            return item instanceof Text && !item.nodeValue.trim() || item instanceof HTMLElement && item.tagName === element.tagName;
        });

        range.deleteContents();

        if (parent.isContentEditable && this.allowed(element.tagName, parent.tagName) && selText.trim() && (!tag || !same)) {
            element.innerText = selText;
            range.insertNode(element);
        } else {
            range.insertNode(this.document.createTextNode(selText));
        }

        parent.normalize();
    }

    /**
     * Returns current selected element or null
     *
     * @return {?HTMLElement}
     */
    getSelectedElement() {
        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const foc = sel.focusNode instanceof Text ? sel.focusNode.parentElement : sel.focusNode;

        return anc instanceof HTMLElement && foc instanceof HTMLElement && anc.isSameNode(foc) && this.content.contains(anc) ? anc : null;
    }

    /**
     * Returns current selected contenteditable or null
     *
     * @return {?HTMLElement}
     */
    getSelectedEditable() {
        const sel = this.window.getSelection();
        const anc = sel.anchorNode instanceof Text ? sel.anchorNode.parentElement : sel.anchorNode;
        const foc = sel.focusNode instanceof Text ? sel.focusNode.parentElement : sel.focusNode;

        if (anc instanceof HTMLElement && foc instanceof HTMLElement) {
            const ancEdit = anc.closest('[contenteditable=true]');
            const focEdit = foc.closest('[contenteditable=true]');

            if (ancEdit instanceof HTMLElement && focEdit instanceof HTMLElement && ancEdit.isSameNode(focEdit) && this.content.contains(ancEdit)) {
                return ancEdit;
            }
        }

        return null;
    }

    /**
     * Returns current selected widget or null
     *
     * @return {?HTMLElement}
     */
    getSelectedWidget() {
        const el = this.getSelectedElement();

        return el ? el.closest('div.editor-content > *') : null;
    }

    /**
     * Filters element
     *
     * @param {HTMLElement} parent
     * @param {Boolean} forceRoot
     */
    filter(parent, forceRoot = false) {
        if (!(parent instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        const isRoot = forceRoot || this.content.isSameNode(parent);
        const parentName = forceRoot ? 'root' : this.getTagName(parent);
        let br;

        parent.normalize();
        Array.from(parent.childNodes).forEach(node => {
            if (node instanceof HTMLElement) {
                node = this.convert(node);
                const name = node.tagName;
                const tag = this.getTag(name);
                const text = node.innerText.trim();

                if (!tag) {
                    parent.removeChild(node);
                } else if (this.allowed(name, parentName) || isRoot && tag.group === 'text') {
                    Array.from(node.attributes).forEach(item => {
                        if (!tag.attributes.includes(item.name)) {
                            node.removeAttribute(item.name);
                        }
                    });

                    if (node.hasChildNodes()) {
                        this.filter(node);
                    }

                    if (!node.hasChildNodes() && !tag.empty && !(node instanceof HTMLTableCellElement)) {
                        parent.removeChild(node);
                    } else if (isRoot && tag.group === 'text') {
                        const p = this.document.createElement('p');
                        p.innerHTML = node.outerHTML;
                        parent.replaceChild(p, node);
                    }
                } else if (isRoot && text) {
                    const p = this.document.createElement('p');
                    p.innerText = text;
                    parent.replaceChild(p, node);
                } else if (text) {
                    parent.replaceChild(this.document.createTextNode(text), node);
                } else {
                    parent.removeChild(node);
                }
            } else if (node instanceof Text) {
                if (isRoot && node.nodeValue.trim()) {
                    const p = this.document.createElement('p');
                    p.innerText = node.nodeValue.trim();
                    parent.replaceChild(p, node);
                } else if (isRoot) {
                    parent.removeChild(node);
                }
            } else {
                parent.removeChild(node);
            }
        });

        // Trim br elements
        while ((br = parent.firstChild) && br instanceof HTMLBRElement || (br = parent.lastChild) && br instanceof HTMLBRElement) {
            parent.removeChild(br);
        }

        // Filter our figure elements with figcaption elements as only child
        parent.querySelectorAll('figure > figcaption:only-child').forEach(node => node.parentElement.removeChild(node));

        // Filter empty tables and table sections
        const sel = 'th:not(:empty), td:not(:empty)';
        parent.querySelectorAll('tbody').forEach(node => !node.querySelector(sel) && node.parentElement.parentElement.removeChild(node.parentElement));
        ['thead', 'tfoot', 'tr'].forEach(item => parent.querySelectorAll(item).forEach(node => !node.querySelector(sel) && node.parentElement.removeChild(node)));
    }

    /**
     * Returns tag name from element considering execption for root element
     *
     * @param {HTMLElement} element
     *
     * @return {String}
     */
    getTagName(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        return this.content.isSameNode(element) ? 'root' : element.tagName;
    }

    /**
     * Returns tag configuration vor given tag name
     *
     * @param {String} name
     *
     * @return {?Tag}
     */
    getTag(name) {
        return this.tags.get(name.toLowerCase()) || null;
    }

    /**
     * Checks if given element is allowed inside given parent element
     *
     * @param {String} name
     * @param {String} parentName
     *
     * @return {Boolean}
     */
    allowed(name, parentName) {
        const tag = this.getTag(name);
        const parentTag = this.getTag(parentName);

        return tag && parentTag && parentTag.children.includes(tag.group);
    }

    /**
     * Checks if given group is allowed inside given parent element
     *
     * @param {String} group
     * @param {String} parentName
     *
     * @return {Boolean}
     */
    allowedGroup(group, parentName) {
        const parentTag = this.getTag(parentName);

        return parentTag && parentTag.children.includes(group);
    }

    /**
     * Converts element
     *
     * @param {HTMLElement} element
     *
     * @return {Node}
     */
    convert(element) {
        if (!(element instanceof HTMLElement)) {
            throw 'No HTML element';
        }

        const converter = this.converters.get(element.tagName.toLowerCase());

        if (!converter) {
            return element;
        }

        const newNode = converter.convert(element);
        element.parentElement.replaceChild(newNode, element);

        return newNode;
    }

    /**
     * Factory method to create a new instance of editor with given configuration
     *
     * @param {HTMLElement} element
     * @param {Object} config
     *
     * @return {Editor}
     */
    static create(element, config = {}) {
        const editor = new Editor(element, config);
        editor.init();

        return editor;
    }
}
<|endoftext|>