<commit_before>/* */
import {funcMeta} from "./utils";
import {Controller} from "./Controller";

const DEFAULT_SUFFIX = "Controller";

/**
@function State
@param {Object}   opts - The options
@param {string}   [opts.name] - The name of the state.
@param {string}   [opts.hidden] - Hide this state from the state path.
@param {string}   [opts.bindTo] - Bind the controller to the provided name.
@param {string}   [opts.url] - The url of the state.
@param {Boolean}  [opts.abstract] - True for abstract states.
@param {string}   [opts.template] - An angular template.
@param {string}   [opts.templateUrl] - A URL to an angular template.
@param {State[]}  [opts.children] - List of child states.
@param {string}   [opts.controllerName] - The name of the controller as seen by angular.
@param {Object}   [opts.resolve] - Any required resolved.
@param {Object}   [opts.views] - State views
@param {string[]} [opts.aliases] - Aliases for the current state.

@example
[@]State({
	url: "/",
	template: `<h1>Hello World</h1>`,
})
class HelloWorld {
}
*/
export function State(opts) {
	if (typeof opts === "function") {
		let constructor = opts; opts = null;
		return register(constructor);
	}

	opts = (opts || {});

	return register;

	function register(constructor) {
		registerLock(constructor);
		Controller(constructor, { name: opts.controllerName });

		let meta = stateMeta(constructor);
		let superMeta = stateMeta(meta.superClass) || { state: {} };

		let prototype = constructor.prototype;
		if (prototype.activate) {
			meta.state.callbacks.onActivate.push(prototype.activate);
		}
		if (prototype.attach) {
			meta.state.callbacks.onAttach.push(prototype.attach);
		}
		if (prototype.detach) {
			meta.state.callbacks.onDetach.push(prototype.detach);
		}

		if (superMeta.state.callbacks) {
			meta.state.callbacks.onActivate = superMeta.state.callbacks.onActivate
				.concat(meta.state.callbacks.onActivate);
			meta.state.callbacks.onAttach = superMeta.state.callbacks.onAttach
				.concat(meta.state.callbacks.onAttach);
			meta.state.callbacks.onDetach = superMeta.state.callbacks.onDetach
				.concat(meta.state.callbacks.onDetach);
		}

		if (opts.children === false) {
			meta.state.children = null;
		} else if (opts.children) {
			meta.state.children = opts.children;
		} else if (superMeta.state.children) {
			meta.state.children = superMeta.state.children;
		}
		if (!meta.state.children) {
			meta.state.children = [];
		}

		if (opts.name) {
			meta.state.name = opts.name;
		} else {
			let name = meta.name;
			name = name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1);
			meta.state.name = name;
		}

		meta.state.bindTo = opts.bindTo;
		meta.state.aliases = (opts.aliases || []).concat(superMeta.state.aliases || []);

		let views = {};
		if (superMeta.state.views) {
			Object.assign(views, superMeta.state.views);
		}
		if (opts.views) {
			Object.assign(views, opts.views);
		}
		if (opts.template === false) {
			views[''] = undefined;
		} else if (opts.template !== undefined) {
			views[''] = { template: opts.template, bindTo: (opts.bindTo || meta.state.name) };
		} else if (opts.templateUrl) {
			views[''] = { templateUrl: opts.templateUrl, bindTo: (opts.bindTo || meta.state.name) };
		}
		meta.state.views = views;

		if (opts.url !== false) {
			if (opts.url !== undefined) {
				meta.state.url = opts.url;
			} else if (superMeta.state.url !== undefined) {
				meta.state.url = superMeta.state.url;
			}
		}

		if (opts.abstract === undefined) {
			meta.state.abstract = superMeta.state.abstract;
		} else if (opts.abstract === true) {
			meta.state.abstract = true;
		} else if (opts.abstract === false) {
			meta.state.abstract = false;
		}

		if (opts.hidden === undefined) {
			meta.state.hidden = superMeta.state.hidden;
		} else if (opts.hidden === true) {
			meta.state.hidden = true;
		} else if (opts.hidden === false) {
			meta.state.hidden = false;
		}

		meta.state.resolve = {};
		if (opts.resolve !== false) {
			Object.assign(meta.state.resolve, superMeta.state.resolve);
			if (opts.resolve) {
				Object.assign(meta.state.resolve, opts.resolve);
			}
		}

	}
}

function stateMeta(constructor) {
	if (!constructor) {
		return null;
	}

	let meta = funcMeta(constructor);

	if (meta.state) {
		return meta;
	}

	meta.state = {
		callbacks: {
			onActivate: [],
			onAttach:   [],
			onDetach:   [],
		},
	};

	return meta;
}

function registerLock(constructor) {
	let meta = stateMeta(constructor);

	if (meta.state.registered) {
		throw "@State() can only be used once!";
	}

	meta.state.registered = true;
}

State.onActivate = function onActivate(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onActivate expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onActivate.push(desc.value);
};

State.onAttach = function onAttach(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onAttach expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onAttach.push(desc.value);
};

State.onDetach = function onDetach(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onDetach expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onDetach.push(desc.value);
};

/**
@function mountAt
@param {String} url
@param {Object} [opts]
@param {String} [opts.name]

@example
SomeState::mountAt("/some/url")
*/
export function mountAt(url, opts={}) {
	let {name} = opts;

	return {
		state:              this,
		url:                url,
		name:               name,
		buildUiRouterState: builder,
	};

	function builder(options) {
		let state = buildUiRouterState(this.state, options);

		if (this.url !== undefined) {
			state.url = url;
		}

		if (this.name !== undefined) {
			state.name = name;
		}

		return state;
	}
}

export function buildUiRouterState(obj, options) {
	if (!obj) {
		return null;
	}

	if (obj.buildUiRouterState) {
		let state = obj.buildUiRouterState(options);
		return state;
	}

	let meta = funcMeta(obj);
	if (!meta.state) {
		throw Error("provided object is not a state");
	}

	let children = [];
	for (let child of meta.state.children) {
		children.push(buildUiRouterState(child, options));
	}

	let views = {};
	for (let key of Object.keys(meta.state.views)) {
		let view = meta.state.views[key];
		if (!view) { continue; }

		views[key] = {
			template:     view.template,
			templateUrl:  view.templateUrl,
			controllerAs: (view.bindTo || meta.state.bindTo || meta.state.name),
			controller:   controllerAttacher,
		};
	}
	if (meta.state.views[''] === undefined) {
		if (children.length > 0) {
			views[''] = {
				template:     '<ui-view></ui-view>',
				controllerAs: (meta.state.bindTo || meta.state.name),
				controller:   controllerAttacher,
			};
		} else {
			views[''] = {
				template:     '',
				controllerAs: (meta.state.bindTo || meta.state.name),
				controller:   controllerAttacher,
			};
		}
	}

	let resolve = {};
	Object.assign(resolve, meta.state.resolve);
	controllerProvider.$inject = ['$q', '$controller', '$locals', '$injector'].concat(Object.keys(resolve));
	controllerAttacher.$inject = [meta.state.name, '$locals', '$injector', '$scope'].concat(Object.keys(resolve));
	resolve[meta.state.name] = controllerProvider;
	for (let alias of (meta.state.aliases || [])) {
		if (!resolve[alias]) {
			resolve[alias] = [meta.state.name, function(mod){ return mod; }];
		}
	}
	resolve.$viewCounter = () => ({ attached: 0, count: Object.keys(views).length });

	controllerProvider.$inject = controllerProvider.$inject.concat(meta.top.$inject || []);
	for (let clb of meta.state.callbacks.onActivate) {
		controllerProvider.$inject = controllerProvider.$inject.concat(clb.$inject || []);
	}

	let state = {
		name:        meta.state.name,
		url:         meta.state.url,
		hiddenState: meta.state.hidden,
		abstract:    meta.state.abstract,
		children:    children,
		resolve:     resolve,
		views:       views,
	};

	return state;

	function controllerProvider($q, $controller, $locals, $injector) {
		return $q(function(ok, err) {
			try {
				let ctrl = $controller(meta.controller.name, $locals);
				let p = $q.when(ctrl);

				for (let clb of meta.state.callbacks.onActivate) {
					p = p.then(() => $injector.invoke(clb, ctrl, $locals));
				}

				p = p.then(() => ctrl);
				ok(p);
			} catch (e) {
				err(e);
			}
		})
		.catch(err => {
			console.error("Error:", err);
			return $q.reject(err);
		});
	}

	function controllerAttacher(ctrl, $locals, $injector, $scope) {
		for (let clb of meta.state.callbacks.onAttach) {
			$injector.invoke(clb, ctrl, $locals);
		}

		$scope.$on("$destroy", function() {
			for (let clb of meta.state.callbacks.onDetach) {
				$injector.invoke(clb, ctrl, $locals);
			}
		});

		return ctrl;
	}
}

export function flattenUiRouterStates(state, acc=[]) {
	acc.push(state);

	if (state.children) {
		let prefix = state.name;
		if (state.hiddenState) {
			if (state.parent) {
				prefix = state.parent.name;
			} else {
				prefix = null;
			}
		}

		for (let child of state.children) {
			if (child) {
				child.parent = state;

				if (prefix && !child.absoluteName) {
					child.name = `${prefix}.${child.name}`;
				}

				flattenUiRouterStates(child, acc);
			} else {
				console.warn(`nil child for ${state.name}`);
			}
		}
	}

	return acc;
}
<commit_msg>feat(state): Allow data to be associated with a state<commit_after>/* */
import {funcMeta} from "./utils";
import {Controller} from "./Controller";

const DEFAULT_SUFFIX = "Controller";

/**
@function State
@param {Object}   opts - The options
@param {string}   [opts.name] - The name of the state.
@param {string}   [opts.hidden] - Hide this state from the state path.
@param {string}   [opts.bindTo] - Bind the controller to the provided name.
@param {string}   [opts.url] - The url of the state.
@param {Boolean}  [opts.abstract] - True for abstract states.
@param {string}   [opts.template] - An angular template.
@param {string}   [opts.templateUrl] - A URL to an angular template.
@param {State[]}  [opts.children] - List of child states.
@param {string}   [opts.controllerName] - The name of the controller as seen by angular.
@param {Object}   [opts.resolve] - Any required resolved.
@param {Object}   [opts.data] - Data to associate with this state.
@param {Object}   [opts.views] - State views
@param {string[]} [opts.aliases] - Aliases for the current state.

@example
[@]State({
	url: "/",
	template: `<h1>Hello World</h1>`,
})
class HelloWorld {
}
*/
export function State(opts) {
	if (typeof opts === "function") {
		let constructor = opts; opts = null;
		return register(constructor);
	}

	opts = (opts || {});

	return register;

	function register(constructor) {
		registerLock(constructor);
		Controller(constructor, { name: opts.controllerName });

		let meta = stateMeta(constructor);
		let superMeta = stateMeta(meta.superClass) || { state: {} };

		let prototype = constructor.prototype;
		if (prototype.activate) {
			meta.state.callbacks.onActivate.push(prototype.activate);
		}
		if (prototype.attach) {
			meta.state.callbacks.onAttach.push(prototype.attach);
		}
		if (prototype.detach) {
			meta.state.callbacks.onDetach.push(prototype.detach);
		}

		if (superMeta.state.callbacks) {
			meta.state.callbacks.onActivate = superMeta.state.callbacks.onActivate
				.concat(meta.state.callbacks.onActivate);
			meta.state.callbacks.onAttach = superMeta.state.callbacks.onAttach
				.concat(meta.state.callbacks.onAttach);
			meta.state.callbacks.onDetach = superMeta.state.callbacks.onDetach
				.concat(meta.state.callbacks.onDetach);
		}

		if (opts.children === false) {
			meta.state.children = null;
		} else if (opts.children) {
			meta.state.children = opts.children;
		} else if (superMeta.state.children) {
			meta.state.children = superMeta.state.children;
		}
		if (!meta.state.children) {
			meta.state.children = [];
		}

		if (opts.name) {
			meta.state.name = opts.name;
		} else {
			let name = meta.name;
			name = name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1);
			meta.state.name = name;
		}

		meta.state.bindTo = opts.bindTo;
		meta.state.aliases = (opts.aliases || []).concat(superMeta.state.aliases || []);

		let views = {};
		if (superMeta.state.views) {
			Object.assign(views, superMeta.state.views);
		}
		if (opts.views) {
			Object.assign(views, opts.views);
		}
		if (opts.template === false) {
			views[''] = undefined;
		} else if (opts.template !== undefined) {
			views[''] = { template: opts.template, bindTo: (opts.bindTo || meta.state.name) };
		} else if (opts.templateUrl) {
			views[''] = { templateUrl: opts.templateUrl, bindTo: (opts.bindTo || meta.state.name) };
		}
		meta.state.views = views;

		if (opts.url !== false) {
			if (opts.url !== undefined) {
				meta.state.url = opts.url;
			} else if (superMeta.state.url !== undefined) {
				meta.state.url = superMeta.state.url;
			}
		}

		if (opts.abstract === undefined) {
			meta.state.abstract = superMeta.state.abstract;
		} else if (opts.abstract === true) {
			meta.state.abstract = true;
		} else if (opts.abstract === false) {
			meta.state.abstract = false;
		}

		if (opts.hidden === undefined) {
			meta.state.hidden = superMeta.state.hidden;
		} else if (opts.hidden === true) {
			meta.state.hidden = true;
		} else if (opts.hidden === false) {
			meta.state.hidden = false;
		}

		meta.state.resolve = {};
		if (opts.resolve !== false) {
			Object.assign(meta.state.resolve, superMeta.state.resolve);
			if (opts.resolve) {
				Object.assign(meta.state.resolve, opts.resolve);
			}
		}

		meta.state.data = {};
		if (opts.data !== false) {
			Object.assign(meta.state.data, superMeta.state.data);
			if (opts.data) {
				Object.assign(meta.state.data, opts.data);
			}
		}

	}
}

function stateMeta(constructor) {
	if (!constructor) {
		return null;
	}

	let meta = funcMeta(constructor);

	if (meta.state) {
		return meta;
	}

	meta.state = {
		callbacks: {
			onActivate: [],
			onAttach:   [],
			onDetach:   [],
		},
	};

	return meta;
}

function registerLock(constructor) {
	let meta = stateMeta(constructor);

	if (meta.state.registered) {
		throw "@State() can only be used once!";
	}

	meta.state.registered = true;
}

State.onActivate = function onActivate(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onActivate expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onActivate.push(desc.value);
};

State.onAttach = function onAttach(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onAttach expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onAttach.push(desc.value);
};

State.onDetach = function onDetach(target, name, desc) {
	if (typeof desc.value !== "function") {
		throw "@State.onDetach expects a function target";
	}

	let meta = stateMeta(target.constructor);
	meta.state.callbacks.onDetach.push(desc.value);
};

/**
@function mountAt
@param {String} url
@param {Object} [opts]
@param {String} [opts.name]

@example
SomeState::mountAt("/some/url")
*/
export function mountAt(url, opts={}) {
	let {name} = opts;

	return {
		state:              this,
		url:                url,
		name:               name,
		buildUiRouterState: builder,
	};

	function builder(options) {
		let state = buildUiRouterState(this.state, options);

		if (this.url !== undefined) {
			state.url = url;
		}

		if (this.name !== undefined) {
			state.name = name;
		}

		return state;
	}
}

export function buildUiRouterState(obj, options) {
	if (!obj) {
		return null;
	}

	if (obj.buildUiRouterState) {
		let state = obj.buildUiRouterState(options);
		return state;
	}

	let meta = funcMeta(obj);
	if (!meta.state) {
		throw Error("provided object is not a state");
	}

	let children = [];
	for (let child of meta.state.children) {
		children.push(buildUiRouterState(child, options));
	}

	let views = {};
	for (let key of Object.keys(meta.state.views)) {
		let view = meta.state.views[key];
		if (!view) { continue; }

		views[key] = {
			template:     view.template,
			templateUrl:  view.templateUrl,
			controllerAs: (view.bindTo || meta.state.bindTo || meta.state.name),
			controller:   controllerAttacher,
		};
	}
	if (meta.state.views[''] === undefined) {
		if (children.length > 0) {
			views[''] = {
				template:     '<ui-view></ui-view>',
				controllerAs: (meta.state.bindTo || meta.state.name),
				controller:   controllerAttacher,
			};
		} else {
			views[''] = {
				template:     '',
				controllerAs: (meta.state.bindTo || meta.state.name),
				controller:   controllerAttacher,
			};
		}
	}

	let resolve = {};
	Object.assign(resolve, meta.state.resolve);
	controllerProvider.$inject = ['$q', '$controller', '$locals', '$injector'].concat(Object.keys(resolve));
	controllerAttacher.$inject = [meta.state.name, '$locals', '$injector', '$scope'].concat(Object.keys(resolve));
	resolve[meta.state.name] = controllerProvider;
	for (let alias of (meta.state.aliases || [])) {
		if (!resolve[alias]) {
			resolve[alias] = [meta.state.name, function(mod){ return mod; }];
		}
	}
	resolve.$viewCounter = () => ({ attached: 0, count: Object.keys(views).length });

	controllerProvider.$inject = controllerProvider.$inject.concat(meta.top.$inject || []);
	for (let clb of meta.state.callbacks.onActivate) {
		controllerProvider.$inject = controllerProvider.$inject.concat(clb.$inject || []);
	}

	let state = {
		name:        meta.state.name,
		url:         meta.state.url,
		hiddenState: meta.state.hidden,
		abstract:    meta.state.abstract,
		children:    children,
		resolve:     resolve,
		views:       views,
		data:        meta.state.data,
	};

	return state;

	function controllerProvider($q, $controller, $locals, $injector) {
		return $q(function(ok, err) {
			try {
				let ctrl = $controller(meta.controller.name, $locals);
				let p = $q.when(ctrl);

				for (let clb of meta.state.callbacks.onActivate) {
					p = p.then(() => $injector.invoke(clb, ctrl, $locals));
				}

				p = p.then(() => ctrl);
				ok(p);
			} catch (e) {
				err(e);
			}
		})
		.catch(err => {
			console.error("Error:", err);
			return $q.reject(err);
		});
	}

	function controllerAttacher(ctrl, $locals, $injector, $scope) {
		for (let clb of meta.state.callbacks.onAttach) {
			$injector.invoke(clb, ctrl, $locals);
		}

		$scope.$on("$destroy", function() {
			for (let clb of meta.state.callbacks.onDetach) {
				$injector.invoke(clb, ctrl, $locals);
			}
		});

		return ctrl;
	}
}

export function flattenUiRouterStates(state, acc=[]) {
	acc.push(state);

	if (state.children) {
		let prefix = state.name;
		if (state.hiddenState) {
			if (state.parent) {
				prefix = state.parent.name;
			} else {
				prefix = null;
			}
		}

		for (let child of state.children) {
			if (child) {
				child.parent = state;

				if (prefix && !child.absoluteName) {
					child.name = `${prefix}.${child.name}`;
				}

				flattenUiRouterStates(child, acc);
			} else {
				console.warn(`nil child for ${state.name}`);
			}
		}
	}

	return acc;
}
<|endoftext|>


<commit_before>// @flow
import * as React from 'react';
import classNames from 'classnames';
import StepItem from './StepItem';
import prefix, { globalKey } from './utils/prefix';
import { mapCloneElement } from './utils/ReactChildren';

type Props = {
  classPrefix: string,
  vertical?: boolean,
  small?: boolean,
  className?: string,
  children: React.ChildrenArray<any>,
  current: number,
  currentStatus?: 'finish' | 'wait' | 'process' | 'error',
}

class Steps extends React.Component<Props> {

  static Item = StepItem;
  static defaultProps = {
    classPrefix: `${globalKey}steps`,
    currentStatus: 'process',
    current: 0
  };

  render() {
    const {
      classPrefix,
      className,
      children,
      vertical,
      small,
      current,
      currentStatus,
      ...rest
    } = this.props;

    const addPrefix: Function = prefix(classPrefix);
    const classes = classNames(classPrefix, {
      [addPrefix('small')]: small,
      [addPrefix('vertical')]: vertical,
      [addPrefix('horizontal')]: !vertical
    }, className);

    const items: React.Node = mapCloneElement(children, (item, index) => {

      const itemProps = {
        stepNumber: index + 1,
        status: 'wait',
        ...item.props
      };

      if (!item.props.status) {

        if (index === current) {
          itemProps.status = currentStatus;
        } else if (index < current) {
          itemProps.status = 'finish';
        }
      }

      return itemProps;
    });

    return (
      <div
        {...rest}
        className={classes}
      >
        {items}
      </div>
    );
  }
}

export default Steps;
<commit_msg>Update Steps<commit_after>// @flow
import * as React from 'react';
import classNames from 'classnames';
import StepItem from './StepItem';
import prefix, { globalKey } from './utils/prefix';
import { mapCloneElement } from './utils/ReactChildren';

type Props = {
  classPrefix: string,
  vertical?: boolean,
  small?: boolean,
  className?: string,
  children: React.ChildrenArray<any>,
  current: number,
  currentStatus?: 'finish' | 'wait' | 'process' | 'error',
}

class Steps extends React.Component<Props> {

  static Item = StepItem;
  static defaultProps = {
    classPrefix: `${globalKey}steps`,
    currentStatus: 'process',
    current: 0
  };

  render() {
    const {
      classPrefix,
      className,
      children,
      vertical,
      small,
      current,
      currentStatus,
      ...rest
    } = this.props;

    const addPrefix: Function = prefix(classPrefix);
    const classes = classNames(classPrefix, {
      [addPrefix('small')]: small,
      [addPrefix('vertical')]: vertical,
      [addPrefix('horizontal')]: !vertical
    }, className);

    const items: React.Node = mapCloneElement(children, (item, index) => {

      const itemProps = {
        stepNumber: index + 1,
        status: 'wait',
        ...item.props
      };

      // fix tail color
      if (currentStatus === 'error' && index === current - 1) {
        itemProps.className = addPrefix('next-error');
      }

      if (!item.props.status) {

        if (index === current) {
          itemProps.status = currentStatus;
        } else if (index < current) {
          itemProps.status = 'finish';
        }
      }

      return itemProps;
    });

    return (
      <div
        {...rest}
        className={classes}
      >
        {items}
      </div>
    );
  }
}

export default Steps;
<|endoftext|>


<commit_before>if (typeof xmltool === 'undefined') {
    /*jshint -W020 */
    xmltool = {};
}

(function($){

    function Xmltool(element, options){
        this.options = $.extend({}, Xmltool.DEFAULTS, options);
        this.$tree = null;
        this.init(element);
        this.message = this.options.message;
    }

    Xmltool.prototype.init = function(element) {
        this.element = element;
        this.$form = $(element);
        this.dtdUrl = this.$form.find(this.options.dtdUrlSelector).val();
        this.setEvents();
        if(typeof this.options.jstreeSelector !== 'undefined') {
            var $tree = $(this.options.jstreeSelector);
            if ($tree.length) {
                this.$tree = $tree;
            }
        }
        if(typeof this.options.jstreeData !== 'undefined') {
            this.loadJstree(this.options.jstreeData);
        }
    };

    Xmltool.prototype.loadJstree = function(data){
        var that = this;
        this.$tree.jstree({
            "json_data" : {'data': [data]},
            "plugins" : ["themes", "json_data", "ui", "crrm", "dnd", "contextmenu"],
            "core": {
                html_titles: true
            },
            "ui" : {select_multiple_modifier: false},
            "crrm" : {
                "move" : {
                    "check_move" : xmltool.jstree.check_move,
                }
            },
            "dnd" : {
                "drop_target" : false,
                "drag_target" : false
            },
            contextmenu: {
                items: function(node){
                    // TODO: support to not display this menu when we don't
                    // have the url to copy/paste.
                    return {
                        copyItem: {
                            label: 'Copy',
                            action: function(n) {
                                xmltool.jstree.copy(n);
                            },
                        },
                        pasteItem: {
                            label: 'Paste',
                            action: function(n) {
                                xmltool.jstree.paste(n);
                            }
                        }
                    };
                }
            }
        }).bind("select_node.jstree", function (e, data) {
            var id = data.rslt.obj.attr("id");
            id = id.replace(/^tree_/, '');
            var elt = $('#' + id.replace(/:/g,'\\:'));
            elt.focus();
            var treeContainer = $(that.options.treeContainerSelector);
            var t =  elt.offset().top + treeContainer.scrollTop() - treeContainer.offset().top - 30;
            treeContainer.animate({
                scrollTop: t,
                }, 1000
            );
        }).bind("loaded.jstree", function (event, data) {
            that.$tree.jstree('open_all');
            that.$tree.height($(that.options.treeContainer).height());
            that.$form.trigger('loadedJstree');

            // To not call this events when we call 'open_all' after loading we
            // defined them here
            $(this).bind('close_node.jstree', function(event, data){
                if (event.isDefaultPrevented()) {
                    return false;
                }
                var id = data.rslt.obj.attr("id");
                id = id.replace(/^tree_/, '');
                var elt = $('#' + id.replace(/:/g,'\\:') + ' > .panel-collapse');
                if(!elt.data('bs.collapse')) {
                    elt.collapse({'toggle': false});
                }
                elt.collapse('hide');
                event.preventDefault();
            }).bind('open_node.jstree', function(event, data){
                if (event.isDefaultPrevented()) {
                    return false;
                }
                var id = data.rslt.obj.attr("id");
                id = id.replace(/^tree_/, '');
                var elt = $('#' + id.replace(/:/g,'\\:') + ' > .panel-collapse');
                if(!elt.data('bs.collapse')) {
                    elt.collapse({'toggle': false});
                }
                elt.collapse('show');
                event.preventDefault();
            });
        }).bind("move_node.jstree", function(event, data){
            that.message('info', 'Moving...', {overlay: true, modal: true});
            setTimeout(function(){
                xmltool.jstree.move_node(event, data);
                that.message('success', 'Moved!');
            }, 50);
        });
    };

    Xmltool.prototype.add_node = function(data){
            var jstree_data = $(data.jstree_data);
            var previous = $(data.previous);

            for(var i=0; i < previous.length; i++){
                var position = previous[i][0];
                var selector = xmltool.utils.escape_id(previous[i][1]);
                var parentobj = $(selector + ':last');
                if (parentobj.length > 1){
                    this.message('error', "Can't add a node in the tree");
                    return false;
                }
                if (parentobj.length){
                    // Create node
                    xmltool.jstree.create_nodes(this.$tree, jstree_data[0], parentobj, position);
                    break;
                }
            }
        };

    Xmltool.prototype.addElement = function($btn, elt_id){
        if ($btn.is('select')) {
            elt_id = $btn.val();
        }
        else {
            elt_id = $btn.data('elt-id');
        }
        var that = this;
        var params = {
            elt_id: elt_id,
            dtd_url: this.dtdUrl
        };
        $.ajax({
            type: 'GET',
            url: this.options.add_element_url,
            data: params,
            dataType: 'json',
            success: function(data, textStatus, jqXHR){
                var objs = $(data.html);
                if($btn.hasClass('btn-list')){
                    $btn.after(objs);
                    // Last is the button to add more
                    var last = $(objs[objs.length-1]);
                    var nexts = last.nextAll();
                    // TODO: Make sure this is correclty tested
                    var longprefix = xmltool.utils.get_prefix(elt_id);
                    var prefix = xmltool.utils.get_prefix(longprefix);
                    var index = xmltool.utils.get_index(longprefix);
                    xmltool.utils.increment_id(prefix, nexts, index, 2);
                }
                else {
                    $btn.replaceWith(objs);
                }

                if ($btn.is('select')) {
                    $btn.val($btn.find('option:first').val());
                }

                //jstree
                    that.add_node(data);
            },
            error: function(jqXHR, textStatus, errorThrown){
                var msg = jqXHR.status + ' ' + jqXHR.statusText;
                that.message('error', msg);
            }
        });
    };

    Xmltool.prototype.removeElement = function($btn){
        var $addBtn = $btn.prev('.btn-add');
        var $parent = this.$form.find(xmltool.utils.escape_id($btn.data('target')));
        if($btn.hasClass('btn-list')) {
            var nexts = $parent.nextAll();
            // Remove the add button
            // NOTE: we delete the button after the parent to keep indexing
            // consistent.
            $parent.next().remove();
            $parent.remove();
            var longprefix = xmltool.utils.get_prefix($parent.attr('id'));
            var prefix = xmltool.utils.get_prefix(longprefix);
            var index = xmltool.utils.get_index(longprefix);
            xmltool.utils.decrement_id(prefix, nexts, index, 2, 1);
        }
        else {
            $parent.replaceWith($addBtn);
            $addBtn.removeClass('hidden');
        }
        this.delete_node('tree_' + $parent.attr('id'));
    };

    Xmltool.prototype.delete_node = function(node_id){
        var node = this.$tree.find('#' + xmltool.utils.escape_id(node_id));
        if (node.length > 1){
            this.message('error', "Can't delete a node in the tree");
        }
        var css_class = node.attr('class').split(' ')[0];
        var nexts = node.nextAll('.'+xmltool.utils.escape_id(css_class));
        var longprefix = xmltool.utils.get_prefix(node.attr('id'));
        var prefix = xmltool.utils.get_prefix(longprefix);
        var index = xmltool.utils.get_index(longprefix);
        xmltool.utils.decrement_id(prefix, nexts, index);
        this.$tree.jstree('delete_node', node);
    };

    Xmltool.prototype.setEvents = function(){
        var that = this;

        $(this.$form).on('shown.bs.collapse', '.panel-collapse', function (e) {
            if (e.isDefaultPrevented()) {
                return false;
            }
            var id = $(this).attr('id');
            id = id.replace(/^collapse-/, '');
            var o = $('#tree_' + xmltool.utils.escape_id(id));
            that.$tree.jstree("open_node", o);
            e.preventDefault();
        }).on('hidden.bs.collapse', '.panel-collapse', function (e) {
            if (e.isDefaultPrevented()) {
                return false;
            }
            var id = $(this).attr('id');
            id = id.replace(/^collapse-/, '');
            var o = $('#tree_' + xmltool.utils.escape_id(id));
            that.$tree.jstree("close_node", o);
            e.preventDefault();
        });

        $(this.$form).on('focus', 'textarea.form-control', function(){
            var id = xmltool.utils.escape_id($(this).parent().attr('id'));
            that.$tree.jstree('hover_node', $('#tree_' + id));
            $(this).on('keyup.xmltool', function(){
                // TODO: this method should be improved to make
                // sure the user has made an update
                that.$form.trigger('field_change.xmltool');
            });
        }).on('blur', 'textarea.form-control', function(){
            var id = xmltool.utils.escape_id($(this).parent().attr('id'));
            var a = $('#tree_' + id).find('a');
            var elt = a.find('._tree_text');
            if (elt.length === 0){
                elt = $('<span class="_tree_text"/>').appendTo(a);
            }
            if($(this).val()){
                elt.text(' (' + xmltool.utils.truncate($(this).val(), 30) + ')');
            }
            else{
                elt.text('');
            }
            $(this).unbind('keyup.xmltool');
        }).on('mouseenter focus', '.contenteditable', function(){
            $(this).ckeditor({removePlugins: 'toolbar'});
            $(this).contenteditablesync({
                getContent: function($element) {
                    var s = $element.ckeditorGet().getData();
                    return xmltool.utils.update_contenteditable_eol(s);
                }
            });
        }).on('click', '.btn-delete', function(e){
            e.preventDefault();
            that.removeElement($(this));
            // We need to return false because of bootstrap collapsable. It
            // doesn't handle 'preventDefault'.
            return false;
        }).on('click', 'a.btn-add', function(e){
            e.preventDefault();
            that.addElement($(this));
            return false;
        }).on('change', 'select.btn-add', function(e){
            e.preventDefault();
            that.addElement($(this));
            return false;
        }).on('click', '.btn-comment',function(e){
            e.preventDefault();
            var self = $(this);

            var comment_textarea = self.next('._comment');

            if (!comment_textarea.length){
                comment_textarea = $('<textarea>').attr('name', self.data('comment-name')).addClass('_comment').addClass('form-control');
                self.after(comment_textarea);
            }
            // Create the dialog
            var modal = self.data('modal');
            if(modal === undefined){
                $.ajax({
                    type: 'GET',
                    url: that.options.comment_modal_url,
                    data: {'comment': comment_textarea.val()},
                    dataType: 'json',
                    async: false,
                    success: function(data, textStatus, jqXHR){
                        modal = $(data.content);
                        self.data('modal', modal);
                        modal.find('.submit').click(function(){
                            var value = modal.find('textarea').val();
                            comment_textarea.val(value);
                            that.options.close_dialog(modal);

                            self.attr('title', value);
                            if (value){
                                self.addClass('has-comment');
                            }
                            else{
                                self.removeClass('has-comment');
                            }
                            return false;
                        });
                    },
                    error: function(jqXHR, textStatus, errorThrown){
                        var msg = jqXHR.status + ' ' + jqXHR.statusText;
                        that.message('error', msg);
                    }
                });
            }
            that.options.open_dialog(modal);
            return false;
        });
    };

    Xmltool.DEFAULTS = {
        open_dialog: function(dialog){
            dialog.modal('show');
        },
        close_dialog: function(dialog){
            dialog.modal('hide');
        },
        message: function(type, msg, options) {
            alert(type + ': ' + msg);
        },
        treeContainerSelector: 'body',
        dtdUrlSelector: '#_xml_dtd_url'
    };

    $.fn.xmltool = function(options){
        return this.each(function(){
            var $this = $(this);
            var data = $this.data('xmltool');
            if (!data) {
                data = new Xmltool(this, options);
                $this.data('xmltool', data);
            }
        });
    };
})(window.jQuery);
<commit_msg>Also sync the contenteditable content in the tree<commit_after>if (typeof xmltool === 'undefined') {
    /*jshint -W020 */
    xmltool = {};
}

(function($){

    function Xmltool(element, options){
        this.options = $.extend({}, Xmltool.DEFAULTS, options);
        this.$tree = null;
        this.init(element);
        this.message = this.options.message;
    }

    Xmltool.prototype.init = function(element) {
        this.element = element;
        this.$form = $(element);
        this.dtdUrl = this.$form.find(this.options.dtdUrlSelector).val();
        this.setEvents();
        if(typeof this.options.jstreeSelector !== 'undefined') {
            var $tree = $(this.options.jstreeSelector);
            if ($tree.length) {
                this.$tree = $tree;
            }
        }
        if(typeof this.options.jstreeData !== 'undefined') {
            this.loadJstree(this.options.jstreeData);
        }
    };

    Xmltool.prototype.loadJstree = function(data){
        var that = this;
        this.$tree.jstree({
            "json_data" : {'data': [data]},
            "plugins" : ["themes", "json_data", "ui", "crrm", "dnd", "contextmenu"],
            "core": {
                html_titles: true
            },
            "ui" : {select_multiple_modifier: false},
            "crrm" : {
                "move" : {
                    "check_move" : xmltool.jstree.check_move,
                }
            },
            "dnd" : {
                "drop_target" : false,
                "drag_target" : false
            },
            contextmenu: {
                items: function(node){
                    // TODO: support to not display this menu when we don't
                    // have the url to copy/paste.
                    return {
                        copyItem: {
                            label: 'Copy',
                            action: function(n) {
                                xmltool.jstree.copy(n);
                            },
                        },
                        pasteItem: {
                            label: 'Paste',
                            action: function(n) {
                                xmltool.jstree.paste(n);
                            }
                        }
                    };
                }
            }
        }).bind("select_node.jstree", function (e, data) {
            var id = data.rslt.obj.attr("id");
            id = id.replace(/^tree_/, '');
            var elt = $('#' + id.replace(/:/g,'\\:'));
            elt.focus();
            var treeContainer = $(that.options.treeContainerSelector);
            var t =  elt.offset().top + treeContainer.scrollTop() - treeContainer.offset().top - 30;
            treeContainer.animate({
                scrollTop: t,
                }, 1000
            );
        }).bind("loaded.jstree", function (event, data) {
            that.$tree.jstree('open_all');
            that.$tree.height($(that.options.treeContainer).height());
            that.$form.trigger('loadedJstree');

            // To not call this events when we call 'open_all' after loading we
            // defined them here
            $(this).bind('close_node.jstree', function(event, data){
                if (event.isDefaultPrevented()) {
                    return false;
                }
                var id = data.rslt.obj.attr("id");
                id = id.replace(/^tree_/, '');
                var elt = $('#' + id.replace(/:/g,'\\:') + ' > .panel-collapse');
                if(!elt.data('bs.collapse')) {
                    elt.collapse({'toggle': false});
                }
                elt.collapse('hide');
                event.preventDefault();
            }).bind('open_node.jstree', function(event, data){
                if (event.isDefaultPrevented()) {
                    return false;
                }
                var id = data.rslt.obj.attr("id");
                id = id.replace(/^tree_/, '');
                var elt = $('#' + id.replace(/:/g,'\\:') + ' > .panel-collapse');
                if(!elt.data('bs.collapse')) {
                    elt.collapse({'toggle': false});
                }
                elt.collapse('show');
                event.preventDefault();
            });
        }).bind("move_node.jstree", function(event, data){
            that.message('info', 'Moving...', {overlay: true, modal: true});
            setTimeout(function(){
                xmltool.jstree.move_node(event, data);
                that.message('success', 'Moved!');
            }, 50);
        });
    };

    Xmltool.prototype.add_node = function(data){
            var jstree_data = $(data.jstree_data);
            var previous = $(data.previous);

            for(var i=0; i < previous.length; i++){
                var position = previous[i][0];
                var selector = xmltool.utils.escape_id(previous[i][1]);
                var parentobj = $(selector + ':last');
                if (parentobj.length > 1){
                    this.message('error', "Can't add a node in the tree");
                    return false;
                }
                if (parentobj.length){
                    // Create node
                    xmltool.jstree.create_nodes(this.$tree, jstree_data[0], parentobj, position);
                    break;
                }
            }
        };

    Xmltool.prototype.addElement = function($btn, elt_id){
        if ($btn.is('select')) {
            elt_id = $btn.val();
        }
        else {
            elt_id = $btn.data('elt-id');
        }
        var that = this;
        var params = {
            elt_id: elt_id,
            dtd_url: this.dtdUrl
        };
        $.ajax({
            type: 'GET',
            url: this.options.add_element_url,
            data: params,
            dataType: 'json',
            success: function(data, textStatus, jqXHR){
                var objs = $(data.html);
                if($btn.hasClass('btn-list')){
                    $btn.after(objs);
                    // Last is the button to add more
                    var last = $(objs[objs.length-1]);
                    var nexts = last.nextAll();
                    // TODO: Make sure this is correclty tested
                    var longprefix = xmltool.utils.get_prefix(elt_id);
                    var prefix = xmltool.utils.get_prefix(longprefix);
                    var index = xmltool.utils.get_index(longprefix);
                    xmltool.utils.increment_id(prefix, nexts, index, 2);
                }
                else {
                    $btn.replaceWith(objs);
                }

                if ($btn.is('select')) {
                    $btn.val($btn.find('option:first').val());
                }

                //jstree
                    that.add_node(data);
            },
            error: function(jqXHR, textStatus, errorThrown){
                var msg = jqXHR.status + ' ' + jqXHR.statusText;
                that.message('error', msg);
            }
        });
    };

    Xmltool.prototype.removeElement = function($btn){
        var $addBtn = $btn.prev('.btn-add');
        var $parent = this.$form.find(xmltool.utils.escape_id($btn.data('target')));
        if($btn.hasClass('btn-list')) {
            var nexts = $parent.nextAll();
            // Remove the add button
            // NOTE: we delete the button after the parent to keep indexing
            // consistent.
            $parent.next().remove();
            $parent.remove();
            var longprefix = xmltool.utils.get_prefix($parent.attr('id'));
            var prefix = xmltool.utils.get_prefix(longprefix);
            var index = xmltool.utils.get_index(longprefix);
            xmltool.utils.decrement_id(prefix, nexts, index, 2, 1);
        }
        else {
            $parent.replaceWith($addBtn);
            $addBtn.removeClass('hidden');
        }
        this.delete_node('tree_' + $parent.attr('id'));
    };

    Xmltool.prototype.delete_node = function(node_id){
        var node = this.$tree.find('#' + xmltool.utils.escape_id(node_id));
        if (node.length > 1){
            this.message('error', "Can't delete a node in the tree");
        }
        var css_class = node.attr('class').split(' ')[0];
        var nexts = node.nextAll('.'+xmltool.utils.escape_id(css_class));
        var longprefix = xmltool.utils.get_prefix(node.attr('id'));
        var prefix = xmltool.utils.get_prefix(longprefix);
        var index = xmltool.utils.get_index(longprefix);
        xmltool.utils.decrement_id(prefix, nexts, index);
        this.$tree.jstree('delete_node', node);
    };

    Xmltool.prototype.setEvents = function(){
        var that = this;

        $(this.$form).on('shown.bs.collapse', '.panel-collapse', function (e) {
            if (e.isDefaultPrevented()) {
                return false;
            }
            var id = $(this).attr('id');
            id = id.replace(/^collapse-/, '');
            var o = $('#tree_' + xmltool.utils.escape_id(id));
            that.$tree.jstree("open_node", o);
            e.preventDefault();
        }).on('hidden.bs.collapse', '.panel-collapse', function (e) {
            if (e.isDefaultPrevented()) {
                return false;
            }
            var id = $(this).attr('id');
            id = id.replace(/^collapse-/, '');
            var o = $('#tree_' + xmltool.utils.escape_id(id));
            that.$tree.jstree("close_node", o);
            e.preventDefault();
        });

        $(this.$form).on('focus', 'textarea.form-control', function(){
            var id = xmltool.utils.escape_id($(this).parent().attr('id'));
            that.$tree.jstree('hover_node', $('#tree_' + id));
            $(this).on('keyup.xmltool', function(){
                // TODO: this method should be improved to make
                // sure the user has made an update
                that.$form.trigger('field_change.xmltool');
            });
        }).on('blur', 'textarea.form-control', function(){
            var id = xmltool.utils.escape_id($(this).parent().attr('id'));
            var a = $('#tree_' + id).find('a');
            var elt = a.find('._tree_text');
            if (elt.length === 0){
                elt = $('<span class="_tree_text"/>').appendTo(a);
            }
            if($(this).val()){
                elt.text(' (' + xmltool.utils.truncate($(this).val(), 30) + ')');
            }
            else{
                elt.text('');
            }
            $(this).unbind('keyup.xmltool');
        }).on('blur', '.contenteditable', function(){
            // TODO: refactor this function with the previous one, the
            // difference is the way to get the string.
            var id = xmltool.utils.escape_id($(this).parent().attr('id'));
            var a = $('#tree_' + id).find('a');
            var elt = a.find('._tree_text');
            if (elt.length === 0){
                elt = $('<span class="_tree_text"/>').appendTo(a);
            }
            var s = $(this).ckeditorGet().getData();
            if(s){
                s = xmltool.utils.update_contenteditable_eol(s);
                elt.text(' (' + xmltool.utils.truncate(s, 30) + ')');
            }
            else{
                elt.text('');
            }
        }).on('mouseenter focus', '.contenteditable', function(){
            $(this).ckeditor({removePlugins: 'toolbar'});
            $(this).contenteditablesync({
                getContent: function($element) {
                    var s = $element.ckeditorGet().getData();
                    return xmltool.utils.update_contenteditable_eol(s);
                }
            });
        }).on('click', '.btn-delete', function(e){
            e.preventDefault();
            that.removeElement($(this));
            // We need to return false because of bootstrap collapsable. It
            // doesn't handle 'preventDefault'.
            return false;
        }).on('click', 'a.btn-add', function(e){
            e.preventDefault();
            that.addElement($(this));
            return false;
        }).on('change', 'select.btn-add', function(e){
            e.preventDefault();
            that.addElement($(this));
            return false;
        }).on('click', '.btn-comment',function(e){
            e.preventDefault();
            var self = $(this);

            var comment_textarea = self.next('._comment');

            if (!comment_textarea.length){
                comment_textarea = $('<textarea>').attr('name', self.data('comment-name')).addClass('_comment').addClass('form-control');
                self.after(comment_textarea);
            }
            // Create the dialog
            var modal = self.data('modal');
            if(modal === undefined){
                $.ajax({
                    type: 'GET',
                    url: that.options.comment_modal_url,
                    data: {'comment': comment_textarea.val()},
                    dataType: 'json',
                    async: false,
                    success: function(data, textStatus, jqXHR){
                        modal = $(data.content);
                        self.data('modal', modal);
                        modal.find('.submit').click(function(){
                            var value = modal.find('textarea').val();
                            comment_textarea.val(value);
                            that.options.close_dialog(modal);

                            self.attr('title', value);
                            if (value){
                                self.addClass('has-comment');
                            }
                            else{
                                self.removeClass('has-comment');
                            }
                            return false;
                        });
                    },
                    error: function(jqXHR, textStatus, errorThrown){
                        var msg = jqXHR.status + ' ' + jqXHR.statusText;
                        that.message('error', msg);
                    }
                });
            }
            that.options.open_dialog(modal);
            return false;
        });
    };

    Xmltool.DEFAULTS = {
        open_dialog: function(dialog){
            dialog.modal('show');
        },
        close_dialog: function(dialog){
            dialog.modal('hide');
        },
        message: function(type, msg, options) {
            alert(type + ': ' + msg);
        },
        treeContainerSelector: 'body',
        dtdUrlSelector: '#_xml_dtd_url'
    };

    $.fn.xmltool = function(options){
        return this.each(function(){
            var $this = $(this);
            var data = $this.data('xmltool');
            if (!data) {
                data = new Xmltool(this, options);
                $this.data('xmltool', data);
            }
        });
    };
})(window.jQuery);
<|endoftext|>


<commit_before>import Ajv from 'ajv';
import isEqual from 'lodash.isequal';
import { withComponent, shadow, props } from 'skatejs';
import merge from 'lodash.merge';
import {
  deepEquals,
  extend,
  findParentTag,
  hasNativeShadowDomSupport,
} from '../utils';

import { store } from '../store.js';

export function BoltBase(Base = HTMLElement) {
  return class extends Base {
    constructor(self) {
      super(self);
      this._wasInitiallyRendered = false;
      return self;
    }

    async shouldReRender(props, state, prevProps, prevState) {
      return !deepEquals(props, prevProps) || !deepEquals(state, prevState);
    }

    async shouldUpdate(prevProps, prevState) {
      return this.shouldReRender(this, prevProps, prevState);
    }

    connectedCallback() {
      super.connectedCallback && super.connectedCallback;
      this._storeUnsubscribe = store.subscribe(() =>
        this.stateChanged(store.getState()),
      );
      this.stateChanged(store.getState());
    }

    stateChanged(state) {}

    disconnectedCallback() {
      this._storeUnsubscribe();
      super.disconnectedCallback && super.disconnectedCallback();
    }

    /**
     * Update component state and schedule a re-render.
     * @param {object} state A dict of state properties to be shallowly merged
     * 	into the current state, or a function that will produce such a dict. The
     * 	function is called with the current state and props.
     * @param {() => void} callback A function to be called once component state is
     * 	updated
     */
    async setState(state, callback) {
      // @todo: review to see which merge we should be using here.
      // this.state = merge(this.state || {}, state);
      this.state = extend(
        extend({}, this.state),
        typeof state === 'function' ? state(this.state, this.props) : state,
      );

      if (!this._prevState) {
        this._prevState = this.state;
      }

      if (
        await this.shouldReRender(
          this.props,
          this.state,
          this._prevProps,
          this._prevState,
        )
      ) {
        await this.triggerUpdate();
      }

      if (callback) {
        // await this._renderCallbacks.push(callback); // @todo: review to see if / how we can implement the normal setState callback
        callback();
      }
    }

    setupSlots() {
      // Automatically adjust which inner element inside the custom element gets used as the base when evaluating slotted children. Necessary when including deeply nested slots in the initial HTML being rendered, which might include a few wrapping containers that get removed when the JavaScript kicks in. <-- this is how we get slotted buttons to work!
      const isShadowRootSelector = this.querySelector('[is="shadow-root"]');
      if (isShadowRootSelector) {
        if (isShadowRootSelector.childNodes) {
          this.slots = this._checkSlots(isShadowRootSelector.childNodes);
        } else {
          this.slots = this._checkSlots();
        }
      } else {
        this.slots = this._checkSlots();
      }
    }

    setupShadow() {
      if (
        this.useShadow === false ||
        findParentTag(this, 'FORM') ||
        this.getAttribute('no-shadow') !== null
      ) {
        this.useShadow = false;
      } else {
        this.useShadow = hasNativeShadowDomSupport;
      }
    }

    get renderRoot() {
      // ensure every component instance renders to the light DOM when needed (ex. if nested inside of a form, render to the light DOM)
      // this ensures that things work as expected, even when a component gets removed / re-added to the page
      this.setupShadow();

      // @todo: add debug flag the build to allow conditionally enabling / disabling this extra slot setup check here.
      if (!this.slots) {
        this.setupSlots(); // hotfix to ensure heavily nested elements containing text-nodes like <replace-with-children> re-render consistently in browsers that don't natively support custom elements Fixes wwwd8-2678
      }

      if (hasNativeShadowDomSupport && this.useShadow === true) {
        return super.renderRoot || shadow(this);
      } else {
        return this;
      }
    }

    validateProps(propData) {
      var validatedData = propData;
      const ajv = new Ajv({ useDefaults: 'shared' });

      // remove default strings in prop data so schema validation can fill in the default
      for (let property in validatedData) {
        if (validatedData[property] === '') {
          delete validatedData[property];
        }
      }

      if (this.schema) {
        let isValid = ajv.validate(this.schema, validatedData);

        // bark at any schema validation errors
        if (!isValid) {
          console.log(ajv.errors);
        }
      }

      return validatedData;
    }

    addStyles(stylesheet) {
      let styles = Array.from(stylesheet);
      styles = styles.join(' ');

      if (this.useShadow && this.renderStyles) {
        return this.renderStyles(styles);
      }
    }

    // Inspired by https://codepen.io/jovdb/pen/ddRZKo
    _checkSlots(selector = this.childNodes) {
      const slots = { default: [] };

      // Loop through nodelist
      selector.forEach(function(child, index, nodelist) {
        const slotName = child.getAttribute ? child.getAttribute('slot') : null;

        if (!slotName) {
          slots.default.push(child);
        } else if (slots[slotName]) {
          slots[slotName].push(child);
        } else {
          slots[slotName] = [];
          slots[slotName].push(child);
        }
      });

      return slots;
    }

    rendered() {
      if (!this._wasInitiallyRendered) {
        this._wasInitiallyRendered = true;

        // Fired only once, when the component has finished rendering for the first time.
        this.dispatchEvent(
          new CustomEvent('ready', {
            detail: {
              name: this.tagName.toLowerCase(),
              shadowDom: this.useShadow ? true : false,
            },
            bubbles: true,
          }),
        );
      }

      // Fired every time an element has rendered
      this.dispatchEvent(
        new CustomEvent('rendered', {
          detail: {
            name: this.tagName.toLowerCase(),
            shadowDom: this.useShadow ? true : false,
          },
          bubbles: true,
        }),
      );
    }
  };
}
<commit_msg>refactor: simplify and update new setState method added to the @bolt/core base component<commit_after>import Ajv from 'ajv';
import { withComponent, shadow, props } from 'skatejs';
import * as deepmerge from 'deepmerge';
import isEqual from 'react-fast-compare';
import { findParentTag, hasNativeShadowDomSupport } from '../utils';

export function BoltBase(Base = HTMLElement) {
  return class extends Base {
    constructor(self) {
      super(self);
      this._wasInitiallyRendered = false;
      return self;
    }

    /**
     * Update component state and schedule a re-render.
     * @param {object} state A dict of state properties to be deepy merged
     * 	into the current state
     */
    async setState(state) {
      this.state = deepmerge.all([this.state ? this.state : {}, state]);
      // super.shouldUpdate && super.shouldUpdate();
    }

    setupSlots() {
      // Automatically adjust which inner element inside the custom element gets used as the base when evaluating slotted children. Necessary when including deeply nested slots in the initial HTML being rendered, which might include a few wrapping containers that get removed when the JavaScript kicks in. <-- this is how we get slotted buttons to work!
      const isShadowRootSelector = this.querySelector('[is="shadow-root"]');
      if (isShadowRootSelector) {
        if (isShadowRootSelector.childNodes) {
          this.slots = this._checkSlots(isShadowRootSelector.childNodes);
        } else {
          this.slots = this._checkSlots();
        }
      } else {
        this.slots = this._checkSlots();
      }
    }

    setupShadow() {
      if (
        this.useShadow === false ||
        findParentTag(this, 'FORM') ||
        this.getAttribute('no-shadow') !== null
      ) {
        this.useShadow = false;
      } else {
        this.useShadow = hasNativeShadowDomSupport;
      }
    }

    get renderRoot() {
      // ensure every component instance renders to the light DOM when needed (ex. if nested inside of a form, render to the light DOM)
      // this ensures that things work as expected, even when a component gets removed / re-added to the page
      this.setupShadow();

      // @todo: add debug flag the build to allow conditionally enabling / disabling this extra slot setup check here.
      if (!this.slots) {
        this.setupSlots(); // hotfix to ensure heavily nested elements containing text-nodes like <replace-with-children> re-render consistently in browsers that don't natively support custom elements Fixes wwwd8-2678
      }

      if (hasNativeShadowDomSupport && this.useShadow === true) {
        return super.renderRoot || shadow(this);
      } else {
        return this;
      }
    }

    validateProps(propData) {
      var validatedData = propData;
      const ajv = new Ajv({ useDefaults: 'shared' });

      // remove default strings in prop data so schema validation can fill in the default
      for (let property in validatedData) {
        if (validatedData[property] === '') {
          delete validatedData[property];
        }
      }

      if (this.schema) {
        let isValid = ajv.validate(this.schema, validatedData);

        // bark at any schema validation errors
        if (!isValid) {
          console.log(ajv.errors);
        }
      }

      return validatedData;
    }

    addStyles(stylesheet) {
      let styles = Array.from(stylesheet);
      styles = styles.join(' ');

      if (this.useShadow && this.renderStyles) {
        return this.renderStyles(styles);
      }
    }

    // Inspired by https://codepen.io/jovdb/pen/ddRZKo
    _checkSlots(selector = this.childNodes) {
      const slots = { default: [] };

      // Loop through nodelist
      selector.forEach(function(child, index, nodelist) {
        const slotName = child.getAttribute ? child.getAttribute('slot') : null;

        if (!slotName) {
          slots.default.push(child);
        } else if (slots[slotName]) {
          slots[slotName].push(child);
        } else {
          slots[slotName] = [];
          slots[slotName].push(child);
        }
      });

      return slots;
    }

    rendered() {
      if (!this._wasInitiallyRendered) {
        this._wasInitiallyRendered = true;

        // Fired only once, when the component has finished rendering for the first time.
        this.dispatchEvent(
          new CustomEvent('ready', {
            detail: {
              name: this.tagName.toLowerCase(),
              shadowDom: this.useShadow ? true : false,
            },
            bubbles: true,
          }),
        );
      }

      // Fired every time an element has rendered
      this.dispatchEvent(
        new CustomEvent('rendered', {
          detail: {
            name: this.tagName.toLowerCase(),
            shadowDom: this.useShadow ? true : false,
          },
          bubbles: true,
        }),
      );
    }
  };
}
<|endoftext|>


<commit_before>import React from 'react'
import { Container } from 'react-responsive-grid'
import get from 'lodash/get'

import { rhythm, scale } from '../utils/typography'

import Header from '../components/Header'

import '../assets/scss/app.scss'

class Template extends React.Component {
  getContainerStyle(isRoot) {
    return {
      maxWidth: '100vw',
      paddingTop: isRoot ? 0 : 68
    }
  }
  render() {
    const { location, children } = get(this, 'props')

    let rootPath = `/`
    let isRoot = location.pathname === rootPath

    if (typeof __PREFIX_PATHS__ !== `undefined` && __PREFIX_PATHS__) {
      rootPath = __PATH_PREFIX__ + `/`
    }

    return (
      <Container
        style={this.getContainerStyle(isRoot)}
      >
        <Header isRoot={isRoot} path={location.pathname} />
        {children()}
      </Container>
    )
  }
}

export default Template
<commit_msg>Fixed<commit_after>import React from 'react'
import { Container } from 'react-responsive-grid'
import get from 'lodash/get'

import { rhythm, scale } from '../utils/typography'

import Header from '../components/Header'

import '../assets/scss/app.scss'

class Template extends React.Component {
  getContainerStyle(isRoot) {
    return {
      maxWidth: '100vw',
      paddingTop: isRoot ? 0 : 66
    }
  }
  render() {
    const { location, children } = get(this, 'props')

    let rootPath = `/`
    let isRoot = location.pathname === rootPath

    if (typeof __PREFIX_PATHS__ !== `undefined` && __PREFIX_PATHS__) {
      rootPath = __PATH_PREFIX__ + `/`
    }

    return (
      <Container
        style={this.getContainerStyle(isRoot)}
      >
        <Header isRoot={isRoot} path={location.pathname} />
        {children()}
      </Container>
    )
  }
}

export default Template
<|endoftext|>


<commit_before>const webpack = require('webpack');
const path = require('path');
const buildPath = path.resolve(__dirname, 'build');
const nodeModulesPath = path.resolve(__dirname, 'node_modules');

const config = {
  entry: [path.join(__dirname, '/src/index.js')],
  // Render source-map file for final build
  devtool: 'source-map',
  // output config
  output: {
    path: buildPath, // Path of output file
    filename: 'bundle.js', // Name of output file
  },
  plugins: [
    // for React optimized
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
    }),
    // Minify the bundle
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        // supresses warnings, usually from module minification
        warnings: false,
      },
    }),
    // Allows error warnings but does not stop compiling.
    new webpack.NoErrorsPlugin(),

  ],
  module: {
    loaders: [
      {
        test: /\.js$/, // All .js files
        loaders: ['babel-loader'], // react-hot is like browser sync and babel loads jsx and es6-7
        exclude: [nodeModulesPath],
      },
      {
          test: /\.css$/,
          loader: "style-loader!css-loader"
      },
    ],
  },
}

module.exports = config
<commit_msg>test for heroku<commit_after>const webpack = require('webpack');
const path = require('path');
const buildPath = path.resolve(__dirname, 'build');
const nodeModulesPath = path.resolve(__dirname, 'node_modules');

const config = {
  entry: [path.join(__dirname, '/src/index.js')],
  // Render source-map file for final build
  devtool: 'source-map',
  // output config
  output: {
    path: buildPath, // Path of output file
    filename: 'bundle.js', // Name of output file
    publicPath: '/build/'
  },
  plugins: [
    // for React optimized
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
    }),
    // Minify the bundle
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        // supresses warnings, usually from module minification
        warnings: false,
      },
    }),
    // Allows error warnings but does not stop compiling.
    new webpack.NoErrorsPlugin(),

  ],
  module: {
    loaders: [
      {
        test: /\.js$/, // All .js files
        loaders: ['babel-loader'], // react-hot is like browser sync and babel loads jsx and es6-7
        exclude: [nodeModulesPath],
      },
      {
          test: /\.css$/,
          loader: "style-loader!css-loader"
      },
    ],
  },
}

module.exports = config
<|endoftext|>


<commit_before>require('es6-promise').polyfill();
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

var BUILD_DIR = path.resolve(__dirname, 'public');
var APP_DIR = path.resolve(__dirname, 'client');
var LESS_DIR = path.resolve(__dirname, 'less');

var config = {
    devtool: 'cheap-module-eval-source-map',
    entry: [
        path.join(__dirname, 'client/Application.jsx'),
        LESS_DIR + '/site.less'
    ],
    output: {
        path: BUILD_DIR,
        filename: 'bundle.min.js',
        publicPath: '/'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: '!!pug!views/index.pug',
            inject: 'body',
            filename: 'index.html'
        }),
        new webpack.optimize.UglifyJsPlugin({
            compressor: {
                warnings: false,
                screw_ie8: true
            }
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
        })
    ],
    module: {
        loaders: [{
            test: /\.jsx?/,
            include: APP_DIR,
            loader: 'babel'
        }, {
            test: /\.less$/,
            loader: 'style!css!autoprefixer!less'
        }, {
            test: /.(png|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/, loader: 'url-loader?limit=100000'
        }]
    }
};

module.exports = config;
<commit_msg>update production config<commit_after>require('es6-promise').polyfill();
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

var BUILD_DIR = path.resolve(__dirname, 'public');
var APP_DIR = path.resolve(__dirname, 'client');
var LESS_DIR = path.resolve(__dirname, 'less');

var config = {
    devtool: 'cheap-module-eval-source-map',
    entry: [
        path.join(__dirname, 'client/Application.jsx'),
        LESS_DIR + '/site.less'
    ],
    output: {
        path: BUILD_DIR,
        filename: 'bundle.min.js',
        publicPath: '/'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: '!!pug!views/index.pug',
            inject: 'body',
            filename: 'index.html'
        }),
        new webpack.optimize.UglifyJsPlugin({
            compressor: {
                warnings: false,
                screw_ie8: true
            }
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
        })
    ],
    module: {
        loaders: [
            {
                test: /\.jsx?/,
                include: APP_DIR,
                exclude: /node_modules/,
                loaders: ['babel']
            },
            {
                test: /\.less$/,
                loader: 'style!css!less'
            }, {
                test: /.(png|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/, loader: 'url-loader?limit=100000'
            }]
    }
};

module.exports = config;
<|endoftext|>


<commit_before>Rubpocalypse.Sounds = (function() {

  var soundForName = function(name) {
    return $("#" + name + "-sound").get(0);
  };

  return {
    play: function(name) {
      if (!Modernizr.audio) return;
      var sound = soundForName(name);
      sound.play();
    },
    pause: function(name) {
      if (!Modernizr.audio) return;
      soundForName(name).pause();
    }
  };

})();
<commit_msg>Comment on Safari's utter <audio> failure.<commit_after>Rubpocalypse.Sounds = (function() {

  var soundForName = function(name) {
    return $("#" + name + "-sound").get(0);
  };

  return {
    play: function(name) {
      if (!Modernizr.audio) return;
      var sound = soundForName(name);
      // Can't play the same sound twice on Safari :( Chrome v13+ works.
      // http://jsfiddle.net/toolmantim/xEfSS/3/
      sound.play();
    },
    pause: function(name) {
      if (!Modernizr.audio) return;
      soundForName(name).pause();
    }
  };

})();
<|endoftext|>


<commit_before>function snap(path, f) {
    var s = document.evaluate(path, document, null, 7, null);
    for (var i = 0; i < s.snapshotLength; i++) f(s.snapshotItem(i));
}

var hasStylesheets = false;
var hasAlternates = false;
var titles = {};

snap('//link[contains(@rel, "style") and @title]', function(link) {
    titles[link.title] = true;
    hasStylesheets = true;
    if (link.rel.indexOf('alternate') !== -1)
        hasAlternates = true;
});

chrome.extension.sendRequest({
    styles: hasStylesheets,
    alternates: hasAlternates
});

function selectStyle(title) {
    snap('//link[contains(@rel, "style") and @title]', function(link) {
        link.disabled = true;
        if (link.title === title)
            link.disabled = false;
    });
}

chrome.extension.onRequest.addListener(function(req, src, send) {
    if (req.getTitles)
        send(titles);
    else if (req.selectStyle)
        selectStyle(req.style);
});
<commit_msg>Not gonna use this, we only care about pages w/ alternates<commit_after>function snap(path, f) {
    var s = document.evaluate(path, document, null, 7, null);
    for (var i = 0; i < s.snapshotLength; i++) f(s.snapshotItem(i));
}

var hasAlternates = false;
var titles = {};

snap('//link[contains(@rel, "style") and @title]', function(link) {
    titles[link.title] = true;
    if (link.rel.indexOf('alternate') !== -1)
        hasAlternates = true;
});

chrome.extension.sendRequest({alternates: hasAlternates});

function selectStyle(title) {
    snap('//link[contains(@rel, "style") and @title]', function(link) {
        link.disabled = true;
        if (link.title === title)
            link.disabled = false;
    });
}

chrome.extension.onRequest.addListener(function(req, src, send) {
    if (req.getTitles)
        send(titles);
    else if (req.selectStyle)
        selectStyle(req.style);
});
<|endoftext|>


<commit_before>import { join } from 'path';
import webpack from 'webpack';
import merge from 'webpack-merge';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CleanPlugin from 'clean-webpack-plugin';
import autoprefixer from 'autoprefixer';

const TARGET = process.env.npm_lifecycle_event;
process.env.BABE_ENV = TARGET;

const PATHS = {
  app: join(__dirname, 'src'),
  build: join(__dirname, 'dist'),
  style: join(__dirname, 'src'),
  templates: join(__dirname, 'templates', 'index.jade'),
};

const common = {
  entry: {
    bundle: PATHS.app,
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  output: {
    path: PATHS.build,
    filename: '[name].js',
    publicPath: '/',
  },
  externals: {
    cheerio: 'window',
    'react/lib/ExecutionEnvironment': true,
    'react/lib/ReactContext': true,
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loader: 'babel?cacheDirectory',
        include: PATHS.app,
      },
      {
        test: /\.jade$/,
        loader: 'jade',
      },
    ],
  },
  postcss: () => [autoprefixer],
  sassLoader: {
    includePaths: PATHS.style,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: PATHS.templates,
      appMountId: 'app',
      inject: false,
      title: 'Tic Tac Toe',
      subtitle: 'Try beat it...',
      description: 'An impossible to beat tic tac toe game',
    }),
  ],
};

if (TARGET === 'start' || !TARGET) {
  module.exports = merge(common, {
    devtool: 'source-map',
    devServer: {
      historyApiFallback: true,
      hot: true,
      inline: true,
      progress: true,
      stats: 'errors-only',
      host: process.env.HOST,
      posrt: process.env.PORT,
    },
    module: {
      loaders: [
        {
          test: /\.(scss|sass)$/,
          loaders: ['style', 'css?sourceMap', 'postcss?sourceMap', 'sass?sourceMap'],
          include: PATHS.app,
        },
      ],
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin(),
    ],
  });
}

if (TARGET === 'build') {
  module.exports = merge(common, {
    module: {
      loaders: [
        {
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract('style-loader', ['css-loader', 'postcss-loader', 'sass-loader']),
        },
      ],
    },
    plugins: [
      new ExtractTextPlugin('style.css'),
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production'),
        'process.env.BROWSER': true,
        __DEV__: false,
      }),
      new webpack.optimize.DedupePlugin(),
      new webpack.optimize.OccurrenceOrderPlugin(),
      new CleanPlugin([PATHS.build]),
    ],
  });
}
<commit_msg>Shortened line<commit_after>import { join } from 'path';
import webpack from 'webpack';
import merge from 'webpack-merge';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CleanPlugin from 'clean-webpack-plugin';
import autoprefixer from 'autoprefixer';

const TARGET = process.env.npm_lifecycle_event;
process.env.BABE_ENV = TARGET;

const PATHS = {
  app: join(__dirname, 'src'),
  build: join(__dirname, 'dist'),
  style: join(__dirname, 'src'),
  templates: join(__dirname, 'templates', 'index.jade'),
};

const common = {
  entry: {
    bundle: PATHS.app,
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  output: {
    path: PATHS.build,
    filename: '[name].js',
    publicPath: '/',
  },
  externals: {
    cheerio: 'window',
    'react/lib/ExecutionEnvironment': true,
    'react/lib/ReactContext': true,
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loader: 'babel?cacheDirectory',
        include: PATHS.app,
      },
      {
        test: /\.jade$/,
        loader: 'jade',
      },
    ],
  },
  postcss: () => [autoprefixer],
  sassLoader: {
    includePaths: PATHS.style,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: PATHS.templates,
      appMountId: 'app',
      inject: false,
      title: 'Tic Tac Toe',
      subtitle: 'Try beat it...',
      description: 'An impossible to beat tic tac toe game',
    }),
  ],
};

if (TARGET === 'start' || !TARGET) {
  module.exports = merge(common, {
    devtool: 'source-map',
    devServer: {
      historyApiFallback: true,
      hot: true,
      inline: true,
      progress: true,
      stats: 'errors-only',
      host: process.env.HOST,
      posrt: process.env.PORT,
    },
    module: {
      loaders: [
        {
          test: /\.(scss|sass)$/,
          loaders: ['style', 'css?sourceMap', 'postcss?sourceMap', 'sass?sourceMap'],
          include: PATHS.app,
        },
      ],
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin(),
    ],
  });
}

if (TARGET === 'build') {
  module.exports = merge(common, {
    module: {
      loaders: [
        {
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract('style-loader', [
            'css-loader',
            'postcss-loader',
            'sass-loader',
          ]),
        },
      ],
    },
    plugins: [
      new ExtractTextPlugin('style.css'),
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production'),
        'process.env.BROWSER': true,
        __DEV__: false,
      }),
      new webpack.optimize.DedupePlugin(),
      new webpack.optimize.OccurrenceOrderPlugin(),
      new CleanPlugin([PATHS.build]),
    ],
  });
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   /!\ DO NOT MODIFY THIS FILE /!\
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// create-Inferno-app is installed globally on people's computers. This means
// that it is extremely difficult to have them upgrade the version and
// because there's only one global version installed, it is very prone to
// breaking changes.
//
// The only job of create-Inferno-app is to init the repository and then
// forward all the commands to the local version of create-Inferno-app.
//
// If you need to add a new command, please add it to the scripts/ folder.
//
// The only reason to modify this file is to add more warnings and
// troubleshooting information for the `create-Inferno-app` command.
//
// Do not make breaking changes! We absolutely don't want to have to
// tell people to update their global version of create-Inferno-app.
//
// Also be careful with new language features.
// This file must work on Node 0.10+.
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   /!\ DO NOT MODIFY THIS FILE /!\
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

'use strict';

var chalk = require('chalk');

var currentNodeVersion = process.versions.node
if (currentNodeVersion.split('.')[0] < 4) {
    console.error(
        chalk.red(
            'You are running Node ' + currentNodeVersion + '.\n' +
            'Create Inferno App requires Node 4 or higher. \n' +
            'Please update your version of Node.'
        )
    );
    process.exit(1);
}

var fs = require('fs');
var path = require('path');
var execSync = require('child_process').execSync;
var spawn = require('cross-spawn');
var semver = require('semver');
var pathExists = require('path-exists');

var projectName;

var program = require('commander')
    .version(require('./package.json').version)
    .arguments('<project-directory>')
    .usage(chalk.green('<project-directory>') + ' [options]')
    .action(function (name) {
        projectName = name;
    })
    .option('--verbose', 'print additional logs')
    .option('--scripts-version <alternative-package>', 'use a non-standard version of Inferno-scripts')
    .on('--help', function () {
        console.log('    Only ' + chalk.green('<project-directory>') + ' is required.');
        console.log();
        console.log('    A custom ' + chalk.cyan('--scripts-version') + ' can be one of:');
        console.log('      - a specific npm version: ' + chalk.green('0.8.2'));
        console.log('      - a custom fork published on npm: ' + chalk.green('my-Inferno-scripts'));
        console.log('      - a .tgz archive: ' + chalk.green('https://mysite.com/my-Inferno-scripts-0.8.2.tgz'));
        console.log('    It is not needed unless you specifically want to use a fork.');
        console.log();
        console.log('    If you have any problems, do not hesitate to file an issue:');
        console.log('      ' + chalk.cyan('https://github.com/facebookincubator/create-Inferno-app/issues/new'));
        console.log();
    })
    .parse(process.argv)

if (typeof projectName === 'undefined') {
    console.error('Please specify the project directory:');
    console.log('  ' + chalk.cyan(program.name()) + chalk.green(' <project-directory>'));
    console.log();
    console.log('For example:');
    console.log('  ' + chalk.cyan(program.name()) + chalk.green(' my-Inferno-app'));
    console.log();
    console.log('Run ' + chalk.cyan(program.name() + ' --help') + ' to see all options.');
    process.exit(1);
}

createApp(projectName, program.verbose, program.scriptsVersion);

function createApp(name, verbose, version) {
    var root = path.resolve(name);
    var appName = path.basename(root);

    checkAppName(appName);

    if (!pathExists.sync(name)) {
        fs.mkdirSync(root);
    } else if (!isSafeToCreateProjectIn(root)) {
        console.log('The directory ' + chalk.green(name) + ' contains files that could conflict.');
        console.log('Try using a new directory name.');
        process.exit(1);
    }

    console.log(
        'Creating a new Inferno app in ' + chalk.green(root) + '.'
    );
    console.log();

    var packageJson = {
        name: appName,
        version: '0.1.0',
        private: true,
    };
    fs.writeFileSync(
        path.join(root, 'package.json'),
        JSON.stringify(packageJson, null, 2)
    );
    var originalDirectory = process.cwd();
    process.chdir(root);

    console.log('Installing packages. This might take a couple minutes.');
    console.log('Installing ' + chalk.cyan('Inferno-scripts') + '...');
    console.log();

    run(root, appName, version, verbose, originalDirectory);
}

function shouldUseYarn() {
    try {
        execSync('yarn --version', {stdio: 'ignore'});
        return true;
    } catch (e) {
        return false;
    }
}

function install(packageToInstall, verbose, callback) {
    var command;
    var args;
    if (shouldUseYarn()) {
        command = 'yarn';
        args = [ 'add', '--dev', '--exact', packageToInstall];
    } else {
        command = 'npm';
        args = ['install', '--save-dev', '--save-exact', packageToInstall];
    }

    if (verbose) {
        args.push('--verbose');
    }

    var child = spawn(command, args, {stdio: 'inherit'});
    child.on('close', function(code) {
        callback(code, command, args);
    });
}

function run(root, appName, version, verbose, originalDirectory) {
    var packageToInstall = getInstallPackage(version);
    var packageName = getPackageName(packageToInstall);

    install(packageToInstall, verbose, function(code, command, args) {
        if (code !== 0) {
            console.error(chalk.cyan(command + ' ' + args.join(' ')) + ' failed');
            process.exit(1);
        }

        checkNodeVersion(packageName);

        var scriptsPath = path.resolve(
            process.cwd(),
            'node_modules',
            packageName,
            'scripts',
            'init.js'
        );
        var init = require(scriptsPath);
        init(root, appName, verbose, originalDirectory);
    });
}

function getInstallPackage(version) {
    var packageToInstall = 'Inferno-scripts';
    var validSemver = semver.valid(version);
    if (validSemver) {
        packageToInstall += '@' + validSemver;
    } else if (version) {
        // for tar.gz or alternative paths
        packageToInstall = version;
    }
    return packageToInstall;
}

// Extract package name from tarball url or path.
function getPackageName(installPackage) {
    if (installPackage.indexOf('.tgz') > -1) {
        // The package name could be with or without semver version, e.g. Inferno-scripts-0.2.0-alpha.1.tgz
        // However, this function returns package name only without semver version.
        return installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/)[1];
    } else if (installPackage.indexOf('@') > 0) {
        // Do not match @scope/ when stripping off @version or @tag
        return installPackage.charAt(0) + installPackage.substr(1).split('@')[0];
    }
    return installPackage;
}

function checkNodeVersion(packageName) {
    var packageJsonPath = path.resolve(
        process.cwd(),
        'node_modules',
        packageName,
        'package.json'
    );
    var packageJson = require(packageJsonPath);
    if (!packageJson.engines || !packageJson.engines.node) {
        return;
    }

    if (!semver.satisfies(process.version, packageJson.engines.node)) {
        console.error(
            chalk.red(
                'You are running Node %s.\n' +
                'Create Inferno App requires Node %s or higher. \n' +
                'Please update your version of Node.'
            ),
            process.version,
            packageJson.engines.node
        );
        process.exit(1);
    }
}

function checkAppName(appName) {
    // TODO: there should be a single place that holds the dependencies
    var dependencies = ['inferno', 'inferno-component'];
    var devDependencies = ['inferno-scripts'];
    var allDependencies = dependencies.concat(devDependencies).sort();

    if (allDependencies.indexOf(appName) >= 0) {
        console.error(
            chalk.red(
                'We cannot create a project called ' + chalk.green(appName) + ' because a dependency with the same name exists.\n' +
                'Due to the way npm works, the following names are not allowed:\n\n'
            ) +
            chalk.cyan(
                allDependencies.map(function(depName) {
                    return '  ' + depName;
                }).join('\n')
            ) +
            chalk.red('\n\nPlease choose a different project name.')
        );
        process.exit(1);
    }
}

// If project only contains files generated by GH, it’s safe.
// We also special case IJ-based products .idea because it integrates with CRA:
function isSafeToCreateProjectIn(root) {
    var validFiles = [
        '.DS_Store', 'Thumbs.db', '.git', '.gitignore', '.idea', 'README.md', 'LICENSE'
    ];
    return fs.readdirSync(root)
        .every(function(file) {
            return validFiles.indexOf(file) >= 0;
        });
}<commit_msg>typo fix in installation script<commit_after>#!/usr/bin/env node

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   /!\ DO NOT MODIFY THIS FILE /!\
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// create-Inferno-app is installed globally on people's computers. This means
// that it is extremely difficult to have them upgrade the version and
// because there's only one global version installed, it is very prone to
// breaking changes.
//
// The only job of create-Inferno-app is to init the repository and then
// forward all the commands to the local version of create-Inferno-app.
//
// If you need to add a new command, please add it to the scripts/ folder.
//
// The only reason to modify this file is to add more warnings and
// troubleshooting information for the `create-Inferno-app` command.
//
// Do not make breaking changes! We absolutely don't want to have to
// tell people to update their global version of create-Inferno-app.
//
// Also be careful with new language features.
// This file must work on Node 0.10+.
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//   /!\ DO NOT MODIFY THIS FILE /!\
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

'use strict';

var chalk = require('chalk');

var currentNodeVersion = process.versions.node
if (currentNodeVersion.split('.')[0] < 4) {
    console.error(
        chalk.red(
            'You are running Node ' + currentNodeVersion + '.\n' +
            'Create Inferno App requires Node 4 or higher. \n' +
            'Please update your version of Node.'
        )
    );
    process.exit(1);
}

var fs = require('fs');
var path = require('path');
var execSync = require('child_process').execSync;
var spawn = require('cross-spawn');
var semver = require('semver');
var pathExists = require('path-exists');

var projectName;

var program = require('commander')
    .version(require('./package.json').version)
    .arguments('<project-directory>')
    .usage(chalk.green('<project-directory>') + ' [options]')
    .action(function (name) {
        projectName = name;
    })
    .option('--verbose', 'print additional logs')
    .option('--scripts-version <alternative-package>', 'use a non-standard version of Inferno-scripts')
    .on('--help', function () {
        console.log('    Only ' + chalk.green('<project-directory>') + ' is required.');
        console.log();
        console.log('    A custom ' + chalk.cyan('--scripts-version') + ' can be one of:');
        console.log('      - a specific npm version: ' + chalk.green('0.8.2'));
        console.log('      - a custom fork published on npm: ' + chalk.green('my-Inferno-scripts'));
        console.log('      - a .tgz archive: ' + chalk.green('https://mysite.com/my-Inferno-scripts-0.8.2.tgz'));
        console.log('    It is not needed unless you specifically want to use a fork.');
        console.log();
        console.log('    If you have any problems, do not hesitate to file an issue:');
        console.log('      ' + chalk.cyan('https://github.com/facebookincubator/create-Inferno-app/issues/new'));
        console.log();
    })
    .parse(process.argv)

if (typeof projectName === 'undefined') {
    console.error('Please specify the project directory:');
    console.log('  ' + chalk.cyan(program.name()) + chalk.green(' <project-directory>'));
    console.log();
    console.log('For example:');
    console.log('  ' + chalk.cyan(program.name()) + chalk.green(' my-Inferno-app'));
    console.log();
    console.log('Run ' + chalk.cyan(program.name() + ' --help') + ' to see all options.');
    process.exit(1);
}

createApp(projectName, program.verbose, program.scriptsVersion);

function createApp(name, verbose, version) {
    var root = path.resolve(name);
    var appName = path.basename(root);

    checkAppName(appName);

    if (!pathExists.sync(name)) {
        fs.mkdirSync(root);
    } else if (!isSafeToCreateProjectIn(root)) {
        console.log('The directory ' + chalk.green(name) + ' contains files that could conflict.');
        console.log('Try using a new directory name.');
        process.exit(1);
    }

    console.log(
        'Creating a new Inferno app in ' + chalk.green(root) + '.'
    );
    console.log();

    var packageJson = {
        name: appName,
        version: '0.1.0',
        private: true,
    };
    fs.writeFileSync(
        path.join(root, 'package.json'),
        JSON.stringify(packageJson, null, 2)
    );
    var originalDirectory = process.cwd();
    process.chdir(root);

    console.log('Installing packages. This might take a couple minutes.');
    console.log('Installing ' + chalk.cyan('Inferno-scripts') + '...');
    console.log();

    run(root, appName, version, verbose, originalDirectory);
}

function shouldUseYarn() {
    try {
        execSync('yarn --version', {stdio: 'ignore'});
        return true;
    } catch (e) {
        return false;
    }
}

function install(packageToInstall, verbose, callback) {
    var command;
    var args;
    if (shouldUseYarn()) {
        command = 'yarn';
        args = [ 'add', '--dev', '--exact', packageToInstall];
    } else {
        command = 'npm';
        args = ['install', '--save-dev', '--save-exact', packageToInstall];
    }

    if (verbose) {
        args.push('--verbose');
    }

    var child = spawn(command, args, {stdio: 'inherit'});
    child.on('close', function(code) {
        callback(code, command, args);
    });
}

function run(root, appName, version, verbose, originalDirectory) {
    var packageToInstall = getInstallPackage(version);
    var packageName = getPackageName(packageToInstall);

    install(packageToInstall, verbose, function(code, command, args) {
        if (code !== 0) {
            console.error(chalk.cyan(command + ' ' + args.join(' ')) + ' failed');
            process.exit(1);
        }

        checkNodeVersion(packageName);

        var scriptsPath = path.resolve(
            process.cwd(),
            'node_modules',
            packageName,
            'scripts',
            'init.js'
        );
        var init = require(scriptsPath);
        init(root, appName, verbose, originalDirectory);
    });
}

function getInstallPackage(version) {
    var packageToInstall = 'inferno-scripts';
    var validSemver = semver.valid(version);
    if (validSemver) {
        packageToInstall += '@' + validSemver;
    } else if (version) {
        // for tar.gz or alternative paths
        packageToInstall = version;
    }
    return packageToInstall;
}

// Extract package name from tarball url or path.
function getPackageName(installPackage) {
    if (installPackage.indexOf('.tgz') > -1) {
        // The package name could be with or without semver version, e.g. Inferno-scripts-0.2.0-alpha.1.tgz
        // However, this function returns package name only without semver version.
        return installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/)[1];
    } else if (installPackage.indexOf('@') > 0) {
        // Do not match @scope/ when stripping off @version or @tag
        return installPackage.charAt(0) + installPackage.substr(1).split('@')[0];
    }
    return installPackage;
}

function checkNodeVersion(packageName) {
    var packageJsonPath = path.resolve(
        process.cwd(),
        'node_modules',
        packageName,
        'package.json'
    );
    var packageJson = require(packageJsonPath);
    if (!packageJson.engines || !packageJson.engines.node) {
        return;
    }

    if (!semver.satisfies(process.version, packageJson.engines.node)) {
        console.error(
            chalk.red(
                'You are running Node %s.\n' +
                'Create Inferno App requires Node %s or higher. \n' +
                'Please update your version of Node.'
            ),
            process.version,
            packageJson.engines.node
        );
        process.exit(1);
    }
}

function checkAppName(appName) {
    // TODO: there should be a single place that holds the dependencies
    var dependencies = ['inferno', 'inferno-component'];
    var devDependencies = ['inferno-scripts'];
    var allDependencies = dependencies.concat(devDependencies).sort();

    if (allDependencies.indexOf(appName) >= 0) {
        console.error(
            chalk.red(
                'We cannot create a project called ' + chalk.green(appName) + ' because a dependency with the same name exists.\n' +
                'Due to the way npm works, the following names are not allowed:\n\n'
            ) +
            chalk.cyan(
                allDependencies.map(function(depName) {
                    return '  ' + depName;
                }).join('\n')
            ) +
            chalk.red('\n\nPlease choose a different project name.')
        );
        process.exit(1);
    }
}

// If project only contains files generated by GH, it’s safe.
// We also special case IJ-based products .idea because it integrates with CRA:
function isSafeToCreateProjectIn(root) {
    var validFiles = [
        '.DS_Store', 'Thumbs.db', '.git', '.gitignore', '.idea', 'README.md', 'LICENSE'
    ];
    return fs.readdirSync(root)
        .every(function(file) {
            return validFiles.indexOf(file) >= 0;
        });
}<|endoftext|>