<commit_before>/**
 * Module dependencies.
 */
var spawn = require('child_process').spawn;
var utils = require('./utils.js');
var report = require('./report.js');

/**
 * The default path to the CINEMA 4D Application.
 *
 * @api private
 */
var cinema4d_path = '/Applications/MAXON/CINEMA\ 4D R14/CINEMA\ 4D.app/Contents/MacOS/CINEMA\ 4D';

/**
 * Set the path to the CINEMA 4D Application.
 *
 * @param {String} path
 * @api public
 */
exports.setApplicationPath = function(path) {
  cinema4d_path = path;
}

/**
 * Get the path to the CINEMA 4D Application.
 *
 * @return {String}
 * @api public
 */
exports.getApplicationPath = function() {
  return cinema4d_path;
}

/**
 * This is the main render function.
 *
 * @param {Object} d
 * @api public
 */
exports.render = function(d) {
  // Check if a filepath is set. if no filepath is set, we cannot render...
  if (d.filepath === undefined) {
    utils.log(d, 'No CINEMA 4D file to render. check the examples by running "c4d --help"');
  }
  // Call the render...
  else {
    execCinemaRender(d);
  }
}

/**
 * This function execute the CINEMA 4D commandline interface with -render option.
 *
 * @param {Object} d
 * @api private
 */
function execCinemaRender(d) {
  // Print the d object. used for development and debugging
  //console.log(d);

  // This is the cli options array we need to execute with the spawn function.
  var tmpOptionsArray = checkOptions(d);
  // Save the stdout data to this variable.
  var tmpStdoutData = '';

  // Execute the CINEMA 4D commandline interface.
  var c4dRender = spawn(cinema4d_path, tmpOptionsArray);
  c4dRender.stdout.on('data', function(data) {
    //console.log('VERSION: '+utils.getVersionFromStdout(data.toString()) );
    utils.log(d, data.toString());
    tmpStdoutData += data.toString();
  });
  c4dRender.stderr.on('data', function(data) {
    utils.log(d, 'Error: '+data.toString());
  });
  c4dRender.on('close', function(code) {
    if (d.report) {
      report.write(tmpStdoutData, d.report);
    };
    //utils.log(d, 'Closed with code: '+code);
  });
}

/**
 * Check the Options comming from commander.js
 *
 * @param {Object} d
 * @return {Array}
 * @api private
 */
function checkOptions(d) {
  var tmpOptionsArray = [];

  tmpOptionsArray.push('-render');
  tmpOptionsArray.push(d.filepath);

  // Check the different options...
  if (d.frame !== undefined) {
    tmpOptionsArray.push('-frame');
    var tmpFrame = d.frame.split(',');
    // Check how many parameter we add to the tmpOptionsArray.
    if (tmpFrame.length === 1) {
      tmpOptionsArray.push(tmpFrame[0]);
    }
    if (tmpFrame.length === 2) {
      tmpOptionsArray.push(tmpFrame[0]);
      tmpOptionsArray.push(tmpFrame[1]);
    }
    if (tmpFrame.length === 3) {
      tmpOptionsArray.push(tmpFrame[0]);
      tmpOptionsArray.push(tmpFrame[1]);
      tmpOptionsArray.push(tmpFrame[2]);
    }
  };
  if (d.oimage !== undefined) {
    tmpOptionsArray.push('-oimage');
    tmpOptionsArray.push(d.oimage);
  };
  if (d.omultipass !== undefined) {
    tmpOptionsArray.push('-omultipass');
    tmpOptionsArray.push(d.omultipass);
  };
  if (d.oformat !== undefined) {
    tmpOptionsArray.push('-oformat');
    tmpOptionsArray.push(d.oformat);
  };
  if (d.oresolution !== undefined) {
    tmpOptionsArray.push('-oresolution');
    var tmpResolution = d.oresolution.split(',');
    tmpOptionsArray.push(tmpResolution[0]);
    tmpOptionsArray.push(tmpResolution[1]);
  };
  if (d.threads !== undefined) {
    tmpOptionsArray.push('-threads');
    tmpOptionsArray.push(d.threads);
  };
  if (d.gui === undefined) {
    tmpOptionsArray.push('-nogui');
  };

  return tmpOptionsArray;
}
<commit_msg>src/index.js update log function<commit_after>/**
 * Module dependencies.
 */
var spawn = require('child_process').spawn;
var utils = require('./utils.js');
var report = require('./report.js');

/**
 * The default path to the CINEMA 4D Application.
 *
 * @api private
 */
var cinema4d_path = '/Applications/MAXON/CINEMA\ 4D R14/CINEMA\ 4D.app/Contents/MacOS/CINEMA\ 4D';

/**
 * Set the path to the CINEMA 4D Application.
 *
 * @param {String} path
 * @api public
 */
exports.setApplicationPath = function(path) {
  cinema4d_path = path;
}

/**
 * Get the path to the CINEMA 4D Application.
 *
 * @return {String}
 * @api public
 */
exports.getApplicationPath = function() {
  return cinema4d_path;
}

/**
 * This is the main render function.
 *
 * @param {Object} d
 * @api public
 */
exports.render = function(d) {
  // Check if a filepath is set. if no filepath is set, we cannot render...
  if (d.filepath === undefined) {
    utils.log(d.silent, 'No CINEMA 4D file to render. check the examples by running "c4d --help"');
  }
  // Call the render...
  else {
    execCinemaRender(d);
  }
}

/**
 * This function execute the CINEMA 4D commandline interface with -render option.
 *
 * @param {Object} d
 * @api private
 */
function execCinemaRender(d) {
  // Print the d object. used for development and debugging
  //console.log(d);

  // This is the cli options array we need to execute with the spawn function.
  var tmpOptionsArray = checkOptions(d);
  // Save the stdout data to this variable.
  var tmpStdoutData = '';

  // Execute the CINEMA 4D commandline interface.
  var c4dRender = spawn(cinema4d_path, tmpOptionsArray);
  c4dRender.stdout.on('data', function(data) {
    //console.log('VERSION: '+utils.getVersionFromStdout(data.toString()) );
    utils.log(d.silent, data.toString());
    tmpStdoutData += data.toString();
  });
  c4dRender.stderr.on('data', function(data) {
    utils.log(d.silent, 'Error: '+data.toString());
  });
  c4dRender.on('close', function(code) {
    if (d.report) {
      report.write(tmpStdoutData, d.report);
    };
    //utils.log(d.silent, 'Closed with code: '+code);
  });
}

/**
 * Check the Options comming from commander.js
 *
 * @param {Object} d
 * @return {Array}
 * @api private
 */
function checkOptions(d) {
  var tmpOptionsArray = [];

  tmpOptionsArray.push('-render');
  tmpOptionsArray.push(d.filepath);

  // Check the different options...
  if (d.frame !== undefined) {
    tmpOptionsArray.push('-frame');
    var tmpFrame = d.frame.split(',');
    // Check how many parameter we add to the tmpOptionsArray.
    if (tmpFrame.length === 1) {
      tmpOptionsArray.push(tmpFrame[0]);
    }
    if (tmpFrame.length === 2) {
      tmpOptionsArray.push(tmpFrame[0]);
      tmpOptionsArray.push(tmpFrame[1]);
    }
    if (tmpFrame.length === 3) {
      tmpOptionsArray.push(tmpFrame[0]);
      tmpOptionsArray.push(tmpFrame[1]);
      tmpOptionsArray.push(tmpFrame[2]);
    }
  };
  if (d.oimage !== undefined) {
    tmpOptionsArray.push('-oimage');
    tmpOptionsArray.push(d.oimage);
  };
  if (d.omultipass !== undefined) {
    tmpOptionsArray.push('-omultipass');
    tmpOptionsArray.push(d.omultipass);
  };
  if (d.oformat !== undefined) {
    tmpOptionsArray.push('-oformat');
    tmpOptionsArray.push(d.oformat);
  };
  if (d.oresolution !== undefined) {
    tmpOptionsArray.push('-oresolution');
    var tmpResolution = d.oresolution.split(',');
    tmpOptionsArray.push(tmpResolution[0]);
    tmpOptionsArray.push(tmpResolution[1]);
  };
  if (d.threads !== undefined) {
    tmpOptionsArray.push('-threads');
    tmpOptionsArray.push(d.threads);
  };
  if (d.gui === undefined) {
    tmpOptionsArray.push('-nogui');
  };

  return tmpOptionsArray;
}
<|endoftext|>


<commit_before>/**
@module ember
*/

import { combine, CONSTANT_TAG, DirtyableTag, UpdatableTag } from '@glimmer/reference';
import { meta } from '@ember/-internals/meta';
import {
  get,
  set,
  addObserver,
  removeObserver,
  notifyPropertyChange,
  defineProperty,
  Mixin,
  tagFor,
  computed,
} from '@ember/-internals/metal';
import { setProxy } from '@ember/-internals/utils';
import { assert } from '@ember/debug';

function contentPropertyDidChange(content, contentKey) {
  let key = contentKey.slice(8); // remove "content."
  if (key in this) {
    return;
  } // if shadowed in proxy
  notifyPropertyChange(this, key);
}

export function contentFor(proxy, m) {
  let content = get(proxy, 'content');
  let tag = (m === undefined ? meta(proxy) : m).readableTag();
  if (tag !== undefined) {
    tag.inner.second.inner.update(tagFor(content));
  }
  return content;
}

/**
  `Ember.ProxyMixin` forwards all properties not defined by the proxy itself
  to a proxied `content` object.  See ObjectProxy for more details.

  @class ProxyMixin
  @namespace Ember
  @private
*/
export default Mixin.create({
  /**
    The object whose properties will be forwarded.

    @property content
    @type EmberObject
    @default null
    @private
  */
  content: null,

  init() {
    this._super(...arguments);
    setProxy(this);
    let m = meta(this);
    m.writableTag(() => combine([DirtyableTag.create(), UpdatableTag.create(CONSTANT_TAG)]));
  },

  destroy() {
    this._super(...arguments);
    this.set('content', null);
  },

  isTruthy: computed('content', function() {
    return !!get(this, 'content');
  }),

  willWatchProperty(key) {
    let contentKey = `content.${key}`;
    addObserver(this, contentKey, null, contentPropertyDidChange);
  },

  didUnwatchProperty(key) {
    let contentKey = `content.${key}`;
    removeObserver(this, contentKey, null, contentPropertyDidChange);
  },

  unknownProperty(key) {
    let content = contentFor(this);
    if (content) {
      return get(content, key);
    }
  },

  setUnknownProperty(key, value) {
    let m = meta(this);

    if (m.proto === this) {
      // if marked as prototype then just defineProperty
      // rather than delegate
      defineProperty(this, key, null, value);
      return value;
    }

    let content = contentFor(this, m);

    assert(
      `Cannot delegate set('${key}', ${value}) to the \'content\' property of object proxy ${this}: its 'content' is undefined.`,
      content
    );

    return set(content, key, value);
  },
});
<commit_msg>use willDestroy in stead of destroy<commit_after>/**
@module ember
*/

import { combine, CONSTANT_TAG, DirtyableTag, UpdatableTag } from '@glimmer/reference';
import { meta } from '@ember/-internals/meta';
import {
  get,
  set,
  addObserver,
  removeObserver,
  notifyPropertyChange,
  defineProperty,
  Mixin,
  tagFor,
  computed,
} from '@ember/-internals/metal';
import { setProxy } from '@ember/-internals/utils';
import { assert } from '@ember/debug';

function contentPropertyDidChange(content, contentKey) {
  let key = contentKey.slice(8); // remove "content."
  if (key in this) {
    return;
  } // if shadowed in proxy
  notifyPropertyChange(this, key);
}

export function contentFor(proxy, m) {
  let content = get(proxy, 'content');
  let tag = (m === undefined ? meta(proxy) : m).readableTag();
  if (tag !== undefined) {
    tag.inner.second.inner.update(tagFor(content));
  }
  return content;
}

/**
  `Ember.ProxyMixin` forwards all properties not defined by the proxy itself
  to a proxied `content` object.  See ObjectProxy for more details.

  @class ProxyMixin
  @namespace Ember
  @private
*/
export default Mixin.create({
  /**
    The object whose properties will be forwarded.

    @property content
    @type EmberObject
    @default null
    @private
  */
  content: null,

  init() {
    this._super(...arguments);
    setProxy(this);
    let m = meta(this);
    m.writableTag(() => combine([DirtyableTag.create(), UpdatableTag.create(CONSTANT_TAG)]));
  },

  willDestroy() {
    this.set('content', null);
    this._super(...arguments);
  },

  isTruthy: computed('content', function() {
    return !!get(this, 'content');
  }),

  willWatchProperty(key) {
    let contentKey = `content.${key}`;
    addObserver(this, contentKey, null, contentPropertyDidChange);
  },

  didUnwatchProperty(key) {
    let contentKey = `content.${key}`;
    removeObserver(this, contentKey, null, contentPropertyDidChange);
  },

  unknownProperty(key) {
    let content = contentFor(this);
    if (content) {
      return get(content, key);
    }
  },

  setUnknownProperty(key, value) {
    let m = meta(this);

    if (m.proto === this) {
      // if marked as prototype then just defineProperty
      // rather than delegate
      defineProperty(this, key, null, value);
      return value;
    }

    let content = contentFor(this, m);

    assert(
      `Cannot delegate set('${key}', ${value}) to the \'content\' property of object proxy ${this}: its 'content' is undefined.`,
      content
    );

    return set(content, key, value);
  },
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';

export default class Fetch extends Component {
  static defaultProps = {
    as: 'json',
    fetchFunction: (url, options) => fetch(url, options)
  }

  state = {
    request: {
      url: this.props.url,
      options: this.props.options
    },
    fetch: this.fetch.bind(this),
    clearData: this.clearData.bind(this),
    loading: null,
  };
  cache = {};
  promises = [];

  getOptions(options) {
    return (typeof options === 'function') ? options() : options;
  }

  componentDidMount() {
    const { url, options, manual, onChange } = this.props;
    this.mounted = true;

    if (typeof onChange === 'function') {
      onChange(this.state);
    }

    if (url && !manual) {
      this.fetch(url, options);
    }
  }

  componentDidUpdate(prevProps) {
    const { url, options, manual } = this.props;
    if (url !== prevProps.url && !manual) {
      this.fetch(url, options);
    }
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  fetch(url, options, updateOptions) {
    let { as, cache } = this.props;

    if (url == null) {
      url = this.props.url;
    }

    options = this.getOptions(options || this.props.options)
    const request = { url, options }

    if (cache && this.cache[url]) {
      // Restore cached state
      const promise = this.cache[url];
      promise.then(cachedState => this.update(cachedState, promise, updateOptions));
      this.promises.push(promise);
    } else {
      this.update({ request, loading: true }, null, updateOptions);

      const promise = this.props.fetchFunction(url, options)
        .then(response => {
          return response[as]()
            .then(data   => ({ response, data }))
            .catch(error => ({ response, data: error }))
        })
        .then(({ response, data }) => {
          const newState = {
            request,
            loading: false,
            [response.ok ? 'error' : 'data' ]: undefined, // Clear last response
            [response.ok ? 'data'  : 'error']: data,
            response
          }

          this.update(newState, promise, updateOptions);

          return newState;
        })
        .catch(error => {
          // Catch request errors with no response (CORS issues, etc)
          const newState = {
            request,
            data: undefined,
            error,
            loading: false
          }

          this.update(newState, promise, updateOptions);

          // Rethrow so not to swallow errors, especially from errors within handlers (children func / onChange)
          throw(error);

          return newState
        });

      this.promises.push(promise);

      if (cache) {
        this.cache[url] = promise;
      }

      return promise;
    }
  }

  clearData() {
    this.setState({ data: undefined })
  }

  update(nextState, currentPromise, options = {}) {
    if (currentPromise) {
      // Handle (i.e. ignore) promises resolved out of order from requests
      const index = this.promises.indexOf(currentPromise);
      if (index === -1) {
        // Ignore update as a later request/promise has already been processed
        return;
      }

      // Remove currently resolved promise and any outstanding promises
      // (which will cause them to be ignored when they do resolve/reject)
      this.promises.splice(0, index + 1);
    }

    const { onChange, onDataChange } = this.props;

    let data = undefined;
    if (nextState.data && nextState.data !== this.state.data && typeof onDataChange === 'function') {
      data = onDataChange(nextState.data, options.ignorePreviousData ? undefined : this.state.data)
    }

    if (typeof onChange === 'function') {
      // Always call onChange even if unmounted.  Useful for `POST` requests with a redirect
      onChange({ ...this.state, ...nextState, ...data && { data } });
    }

    // Ignore passing state down if no longer mounted
    if (this.mounted) {
      // If `onDataChange` prop returned a value, we use it for data passed down to the children function
      this.setState({ ...nextState, ...data !== undefined && { data } });
    }
  }

  render() {
    const { children } = this.props;
    return renderChildren(children, this.state);
  }
}

export function renderChildren(children, fetchProps) {
  if (typeof(children) === 'function') {
    const childrenResult = children(fetchProps);
    if (typeof childrenResult === 'function') {
      return renderChildren(childrenResult, fetchProps)
    } else {
      return childrenResult;
    }
  } else if (React.Children.count(children) === 0) {
    return null
  } else {
    // DOM/Component children
    // TODO: Better to check if children count === 1 and return null otherwise (like react-router)?
    //       Currently not possible to support multiple children components/elements (until React fiber)
    return React.Children.only(children)
  }
}
<commit_msg>Also pass updated data to onChange if onDataChange returns a false value (ex. 0)<commit_after>import React, { Component } from 'react';

export default class Fetch extends Component {
  static defaultProps = {
    as: 'json',
    fetchFunction: (url, options) => fetch(url, options)
  }

  state = {
    request: {
      url: this.props.url,
      options: this.props.options
    },
    fetch: this.fetch.bind(this),
    clearData: this.clearData.bind(this),
    loading: null,
  };
  cache = {};
  promises = [];

  getOptions(options) {
    return (typeof options === 'function') ? options() : options;
  }

  componentDidMount() {
    const { url, options, manual, onChange } = this.props;
    this.mounted = true;

    if (typeof onChange === 'function') {
      onChange(this.state);
    }

    if (url && !manual) {
      this.fetch(url, options);
    }
  }

  componentDidUpdate(prevProps) {
    const { url, options, manual } = this.props;
    if (url !== prevProps.url && !manual) {
      this.fetch(url, options);
    }
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  fetch(url, options, updateOptions) {
    let { as, cache } = this.props;

    if (url == null) {
      url = this.props.url;
    }

    options = this.getOptions(options || this.props.options)
    const request = { url, options }

    if (cache && this.cache[url]) {
      // Restore cached state
      const promise = this.cache[url];
      promise.then(cachedState => this.update(cachedState, promise, updateOptions));
      this.promises.push(promise);
    } else {
      this.update({ request, loading: true }, null, updateOptions);

      const promise = this.props.fetchFunction(url, options)
        .then(response => {
          return response[as]()
            .then(data   => ({ response, data }))
            .catch(error => ({ response, data: error }))
        })
        .then(({ response, data }) => {
          const newState = {
            request,
            loading: false,
            [response.ok ? 'error' : 'data' ]: undefined, // Clear last response
            [response.ok ? 'data'  : 'error']: data,
            response
          }

          this.update(newState, promise, updateOptions);

          return newState;
        })
        .catch(error => {
          // Catch request errors with no response (CORS issues, etc)
          const newState = {
            request,
            data: undefined,
            error,
            loading: false
          }

          this.update(newState, promise, updateOptions);

          // Rethrow so not to swallow errors, especially from errors within handlers (children func / onChange)
          throw(error);

          return newState
        });

      this.promises.push(promise);

      if (cache) {
        this.cache[url] = promise;
      }

      return promise;
    }
  }

  clearData() {
    this.setState({ data: undefined })
  }

  update(nextState, currentPromise, options = {}) {
    if (currentPromise) {
      // Handle (i.e. ignore) promises resolved out of order from requests
      const index = this.promises.indexOf(currentPromise);
      if (index === -1) {
        // Ignore update as a later request/promise has already been processed
        return;
      }

      // Remove currently resolved promise and any outstanding promises
      // (which will cause them to be ignored when they do resolve/reject)
      this.promises.splice(0, index + 1);
    }

    const { onChange, onDataChange } = this.props;

    let data = undefined;
    if (nextState.data && nextState.data !== this.state.data && typeof onDataChange === 'function') {
      data = onDataChange(nextState.data, options.ignorePreviousData ? undefined : this.state.data)
    }

    if (typeof onChange === 'function') {
      // Always call onChange even if unmounted.  Useful for `POST` requests with a redirect
      onChange({ ...this.state, ...nextState, ...data !== undefined && { data } });
    }

    // Ignore passing state down if no longer mounted
    if (this.mounted) {
      // If `onDataChange` prop returned a value, we use it for data passed down to the children function
      this.setState({ ...nextState, ...data !== undefined && { data } });
    }
  }

  render() {
    const { children } = this.props;
    return renderChildren(children, this.state);
  }
}

export function renderChildren(children, fetchProps) {
  if (typeof(children) === 'function') {
    const childrenResult = children(fetchProps);
    if (typeof childrenResult === 'function') {
      return renderChildren(childrenResult, fetchProps)
    } else {
      return childrenResult;
    }
  } else if (React.Children.count(children) === 0) {
    return null
  } else {
    // DOM/Component children
    // TODO: Better to check if children count === 1 and return null otherwise (like react-router)?
    //       Currently not possible to support multiple children components/elements (until React fiber)
    return React.Children.only(children)
  }
}
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */
'use strict';

const rgm = require('registry-mixin'),
	ServiceLogger = require('./ServiceLogger'),
	ServiceConfig = require('./ServiceConfig'),
	endpoint = require('kronos-endpoint');

/**
 * provide services and hold service configuration
 */
module.exports = (superclass) => class extends superclass {

	/**
	 * if config is an array entry 0 then entry 0 will be passed to super and all other entries
	 * are handed over as initial config to the config services
	 */
	constructor(config, owner) {
		super(Array.isArray(config) ? config[0] : config, undefined);

		// by default be our own owner
		Object.defineProperty(this, 'owner', {
			value: this
		});

		const loggerService = new ServiceLogger({}, this);
		const configService = new ServiceConfig({}, this);

		rgm.defineRegistryProperties(this, 'serviceFactory', {
			pluralName: 'serviceFactories',
			withCreateInstance: true,
			withEvents: true,
			factoryType: 'new'
		});

		rgm.defineRegistryProperties(this, 'service', {
			hasBeenRegistered: service => {
				// connect log endpoint to logger service
				const logger = this.services.logger;
				if (service.endpoints.log.isOut && logger) {
					service.endpoints.log.connected = logger.endpoints.log;
				}
				return service.autostart ? service.start() : Promise.resolve();
			},
			willBeUnregistered: service => service.stop()
		});

		// let our own logging go into the logger service
		this.registerService(loggerService);

		/**
		 * requests can be an array of config entries
		 */
		this.addEndpoint(new endpoint.ReceiveEndpoint('command', this)).receive = request => {
			const command = request.data ? request.data : JSON.parse(request.payload.read());

			if (Array.isArray(command)) {
				return Promise.all(command.map(c => execCommand(this, c)));
			}

			return execCommand(this, command);
		};

		function execCommand(sp, command) {
			if (command.action === 'list') {
				return Promise.resolve(Object.keys(sp.services).map(name => sp.services[name]).map(s => s.toJSONWithOptions(
					command.options || {})));
			}

			const service = sp.services[command.service];

			if (!service) {
				return Promise.reject(new Error(`Unknown service: ${command.service}`));
			}

			switch (command.action) {
				case 'get':
					return Promise.resolve(service.toJSONWithOptions(command.options || {}));

				case 'start':
					return service.start();

				case 'stop':
					return service.stop();

				case 'restart':
					return service.restart();

				default:
					return Promise.reject(new Error(`Unknown command: ${command.action}`));
			}
		}

		// TODO why do i need to recreate the log endpoint here ?
		this.addEndpoint(new endpoint.SendEndpoint('log', this)).connected = loggerService.endpoints.log;

		// register config service and let it know about the initial config
		this.registerService(configService);

		if (Array.isArray(config)) {
			config.forEach(entry => configService.registerPreservedConfig(entry));
		} else if (config !== undefined) {
			configService.registerPreservedConfig(config);
		}

		this.registerService(this);
	}

	/**
	 * Add a new service based on its configuration
	 * If a service for the name is already present then
	 * its configure() is called.
	 * Otherwise a new service will be created
	 * @param {Object} config with
	 *     name - the service name
	 *     type - the service factory name - defaults to config.name
	 * @param {boolean} waitUntilFactoryPresent waits until someone registers a matching service factory
	 * @return {Promise} resolving to the declared service
	 */
	declareService(config, waitUntilFactoryPresent) {
		const name = config.name;
		const service = this.services[name];

		if (service === undefined) {
			if (config.type === undefined) {
				config.type = name;
			}
			const type = config.type;

			if (this._declareServiceByNamePromises) {
				const p = this._declareServiceByNamePromises.get(name);

				if (p) {
					return p;
				}
			} else {
				this._declareServiceByNamePromises = new Map();
			}

			const insertIntoDeclareByNamePromisesAndDeliver = () => {
				const p = this.registerService(this.createServiceFactoryInstanceFromConfig(config, this)).then(
					s => {
						this._declareServiceByNamePromises.delete(name);
						return Promise.resolve(s);
					});

				this._declareServiceByNamePromises.set(name, p);

				return p;
			};

			if (this.services.config) {
				const pc = this.services.config.preservedConfigs[name];
				if (pc) {
					Object.assign(config, pc);
				}
			}
			// service factory not present: wait until one arrives
			if (waitUntilFactoryPresent && !this.serviceFactories[type]) {
				if (this._declareServiceFactoryByTypePromises) {
					const p = this._declareServiceFactoryByTypePromises.get(type);
					if (p) {
						return p;
					}
				} else {
					this._declareServiceFactoryByTypePromises = new Map();
				}

				const typePromise = new Promise((fullfill, reject) => {
					const listener = factory => {
						if (factory.name === type) {
							this._declareServiceFactoryByTypePromises.delete(type);
							this.removeListener('serviceFactoryRegistered', listener);
							fullfill(factory);
						}
					};

					this.addListener('serviceFactoryRegistered', listener);
				});
				this._declareServiceFactoryByTypePromises.set(type, typePromise);
				return typePromise.then(() => insertIntoDeclareByNamePromisesAndDeliver());
			}

			return insertIntoDeclareByNamePromisesAndDeliver();
		}

		delete config.type;

		const p = service.configure(config).then(() => Promise.resolve(service));
		this._declareServiceByNamePromises.set(name, p);

		return p;
	}

	replaceService(name, newService) {

		/*
		const oldService = this.services[name];
				if (oldService) {
					// TODO take over endpoints
				}
		*/

		return this.registerServiceAs(newService, name);
	}
};
<commit_msg>refactor: declare and use default params<commit_after>/* jslint node: true, esnext: true */
'use strict';

const rgm = require('registry-mixin'),
	ServiceLogger = require('./ServiceLogger'),
	ServiceConfig = require('./ServiceConfig'),
	endpoint = require('kronos-endpoint');

/**
 * provide services and hold service configuration
 */
module.exports = (superclass) => class extends superclass {

	/**
	 * if config is an array entry 0 then entry 0 will be passed to super and all other entries
	 * are handed over as initial config to the config services
	 */
	constructor(config, owner) {
		super(Array.isArray(config) ? config[0] : config, undefined);

		// by default be our own owner
		Object.defineProperty(this, 'owner', {
			value: this
		});

		const loggerService = new ServiceLogger({}, this);
		const configService = new ServiceConfig({}, this);

		rgm.defineRegistryProperties(this, 'serviceFactory', {
			pluralName: 'serviceFactories',
			withCreateInstance: true,
			withEvents: true,
			factoryType: 'new'
		});

		rgm.defineRegistryProperties(this, 'service', {
			hasBeenRegistered: service => {
				// connect log endpoint to logger service
				const logger = this.services.logger;
				if (service.endpoints.log.isOut && logger) {
					service.endpoints.log.connected = logger.endpoints.log;
				}
				return service.autostart ? service.start() : Promise.resolve();
			},
			willBeUnregistered: service => service.stop()
		});

		// let our own logging go into the logger service
		this.registerService(loggerService);

		/**
		 * requests can be an array of config entries
		 */
		this.addEndpoint(new endpoint.ReceiveEndpoint('command', this)).receive = request => {
			const command = request.data ? request.data : JSON.parse(request.payload.read());

			if (Array.isArray(command)) {
				return Promise.all(command.map(c => execCommand(this, c)));
			}

			return execCommand(this, command);
		};

		function execCommand(sp, command) {
			if (command.action === 'list') {
				return Promise.resolve(Object.keys(sp.services).map(name => sp.services[name]).map(s => s.toJSONWithOptions(
					command.options || {})));
			}

			const service = sp.services[command.service];

			if (!service) {
				return Promise.reject(new Error(`Unknown service: ${command.service}`));
			}

			switch (command.action) {
				case 'get':
					return Promise.resolve(service.toJSONWithOptions(command.options));

				case 'start':
					return service.start();

				case 'stop':
					return service.stop();

				case 'restart':
					return service.restart();

				default:
					return Promise.reject(new Error(`Unknown command: ${command.action}`));
			}
		}

		// TODO why do i need to recreate the log endpoint here ?
		this.addEndpoint(new endpoint.SendEndpoint('log', this)).connected = loggerService.endpoints.log;

		// register config service and let it know about the initial config
		this.registerService(configService);

		if (Array.isArray(config)) {
			config.forEach(entry => configService.registerPreservedConfig(entry));
		} else if (config !== undefined) {
			configService.registerPreservedConfig(config);
		}

		this.registerService(this);
	}

	/**
	 * Add a new service based on its configuration
	 * If a service for the name is already present then
	 * its configure() is called.
	 * Otherwise a new service will be created
	 * @param {Object} config with
	 *     name - the service name
	 *     type - the service factory name - defaults to config.name
	 * @param {boolean} waitUntilFactoryPresent waits until someone registers a matching service factory
	 * @return {Promise} resolving to the declared service
	 */
	declareService(config, waitUntilFactoryPresent) {
		const name = config.name;
		const service = this.services[name];

		if (service === undefined) {
			if (config.type === undefined) {
				config.type = name;
			}
			const type = config.type;

			if (this._declareServiceByNamePromises) {
				const p = this._declareServiceByNamePromises.get(name);

				if (p) {
					return p;
				}
			} else {
				this._declareServiceByNamePromises = new Map();
			}

			const insertIntoDeclareByNamePromisesAndDeliver = () => {
				const p = this.registerService(this.createServiceFactoryInstanceFromConfig(config, this)).then(
					s => {
						this._declareServiceByNamePromises.delete(name);
						return Promise.resolve(s);
					});

				this._declareServiceByNamePromises.set(name, p);

				return p;
			};

			if (this.services.config) {
				const pc = this.services.config.preservedConfigs[name];
				if (pc) {
					Object.assign(config, pc);
				}
			}
			// service factory not present: wait until one arrives
			if (waitUntilFactoryPresent && !this.serviceFactories[type]) {
				if (this._declareServiceFactoryByTypePromises) {
					const p = this._declareServiceFactoryByTypePromises.get(type);
					if (p) {
						return p;
					}
				} else {
					this._declareServiceFactoryByTypePromises = new Map();
				}

				const typePromise = new Promise((fullfill, reject) => {
					const listener = factory => {
						if (factory.name === type) {
							this._declareServiceFactoryByTypePromises.delete(type);
							this.removeListener('serviceFactoryRegistered', listener);
							fullfill(factory);
						}
					};

					this.addListener('serviceFactoryRegistered', listener);
				});
				this._declareServiceFactoryByTypePromises.set(type, typePromise);
				return typePromise.then(() => insertIntoDeclareByNamePromisesAndDeliver());
			}

			return insertIntoDeclareByNamePromisesAndDeliver();
		}

		delete config.type;

		const p = service.configure(config).then(() => Promise.resolve(service));
		this._declareServiceByNamePromises.set(name, p);

		return p;
	}

	replaceService(name, newService) {

		/*
		const oldService = this.services[name];
				if (oldService) {
					// TODO take over endpoints
				}
		*/

		return this.registerServiceAs(newService, name);
	}
};
<|endoftext|>


<commit_before>describe('the palindrome canary spec', () => {
  it('shows the infrastructure works', () => {
    true.should.be.true();
  });

  let isPalindrome = (word) => {
    return word === word.split('').reverse().join('');
  };

  describe('palindrome should be', () => {
    it('true for mom', () => {
      isPalindrome('mom').should.be.true();
    });

    it('false for dude', () => {
      isPalindrome('dude').should.be.false();
    });

    it('true for mom mom');
    it('false for dad mom');
    it('true for whitespaces');
    it('error for empty string');
    it('error for not a string');
  });
});
<commit_msg>(1) - pass the next 3 tests<commit_after>describe('the palindrome canary spec', () => {
  it('shows the infrastructure works', () => {
    true.should.be.true();
  });

  let isPalindrome = (word) => {
    return word === word.split('').reverse().join('');
  };

  describe('palindrome should be', () => {
    it('true for mom', () => {
      isPalindrome('mom').should.be.true();
    });

    it('false for dude', () => {
      isPalindrome('dude').should.be.false();
    });

    it('true for mom mom', () => {
      isPalindrome('mom mom').should.be.true();
    });

    it('false for dad mom', () => {
      isPalindrome('dad mom').should.be.false();
    });

    it('true for whitespace', () => {
      isPalindrome('').should.be.true();
      isPalindrome(' ').should.be.true();
      isPalindrome('     ').should.be.true();
    });

    it('error for empty string');
    it('error for not a string');
  });
});
<|endoftext|>


<commit_before>/* eslint-disable */
((global) => {
    class TemplateSelector {
      constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
        this.onClick = this.onClick.bind(this);
        this.dropdown = dropdown;
        this.data = data;
        this.pattern = pattern;
        this.wrapper = wrapper;
        this.editor = editor;
        this.fileEndpoint = fileEndpoint;
        this.$input = $input || $('#file_name');
        this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
        this.buildDropdown();
        this.bindEvents();
        this.onFilenameUpdate();

        this.autosizeUpdateEvent = document.createEvent('Event');
        this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
      }

      buildDropdown() {
        return this.dropdown.glDropdown({
          data: this.data,
          filterable: true,
          selectable: true,
          toggleLabel: this.toggleLabel,
          search: {
            fields: ['name']
          },
          clicked: this.onClick,
          text: function(item) {
            return item.name;
          }
        });
      }

      bindEvents() {
        return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
      }

      toggleLabel(item) {
        return item.name;
      }

      onFilenameUpdate() {
        var filenameMatches;
        if (!this.$input.length) {
          return;
        }
        filenameMatches = this.pattern.test(this.$input.val().trim());
        if (!filenameMatches) {
          this.wrapper.addClass('hidden');
          return;
        }
        return this.wrapper.removeClass('hidden');
      }

      onClick(item, el, e) {
        e.preventDefault();
        return this.requestFile(item);
      }

      requestFile(item) {
        // This `requestFile` method is an abstract method that should
        // be added by all subclasses.
      }

      // To be implemented on the extending class
      // e.g.
      // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
      requestFileSuccess(file, { skipFocus } = {}) {
        const oldValue = this.editor.getValue();
        let newValue = file.content;

        this.editor.setValue(newValue, 1);
        if (!skipFocus) this.editor.focus();

        if (this.editor instanceof jQuery) {
          this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
        }
      }

      startLoadingSpinner() {
        this.dropdownIcon
          .addClass('fa-spinner fa-spin')
          .removeClass('fa-chevron-down');
      }

      stopLoadingSpinner() {
        this.dropdownIcon
          .addClass('fa-chevron-down')
          .removeClass('fa-spinner fa-spin');
      }
    }

    global.TemplateSelector = TemplateSelector;
  })(window.gl || ( window.gl = {}));
<commit_msg>Do nothing if file is undefined<commit_after>/* eslint-disable */
((global) => {
    class TemplateSelector {
      constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
        this.onClick = this.onClick.bind(this);
        this.dropdown = dropdown;
        this.data = data;
        this.pattern = pattern;
        this.wrapper = wrapper;
        this.editor = editor;
        this.fileEndpoint = fileEndpoint;
        this.$input = $input || $('#file_name');
        this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
        this.buildDropdown();
        this.bindEvents();
        this.onFilenameUpdate();

        this.autosizeUpdateEvent = document.createEvent('Event');
        this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
      }

      buildDropdown() {
        return this.dropdown.glDropdown({
          data: this.data,
          filterable: true,
          selectable: true,
          toggleLabel: this.toggleLabel,
          search: {
            fields: ['name']
          },
          clicked: this.onClick,
          text: function(item) {
            return item.name;
          }
        });
      }

      bindEvents() {
        return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
      }

      toggleLabel(item) {
        return item.name;
      }

      onFilenameUpdate() {
        var filenameMatches;
        if (!this.$input.length) {
          return;
        }
        filenameMatches = this.pattern.test(this.$input.val().trim());
        if (!filenameMatches) {
          this.wrapper.addClass('hidden');
          return;
        }
        return this.wrapper.removeClass('hidden');
      }

      onClick(item, el, e) {
        e.preventDefault();
        return this.requestFile(item);
      }

      requestFile(item) {
        // This `requestFile` method is an abstract method that should
        // be added by all subclasses.
      }

      // To be implemented on the extending class
      // e.g.
      // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
      requestFileSuccess(file, { skipFocus } = {}) {
        if (!file) return;

        const oldValue = this.editor.getValue();
        let newValue = file.content;

        this.editor.setValue(newValue, 1);
        if (!skipFocus) this.editor.focus();

        if (this.editor instanceof jQuery) {
          this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
        }
      }

      startLoadingSpinner() {
        this.dropdownIcon
          .addClass('fa-spinner fa-spin')
          .removeClass('fa-chevron-down');
      }

      stopLoadingSpinner() {
        this.dropdownIcon
          .addClass('fa-chevron-down')
          .removeClass('fa-spinner fa-spin');
      }
    }

    global.TemplateSelector = TemplateSelector;
  })(window.gl || ( window.gl = {}));
<|endoftext|>


<commit_before>var express = require('express');
var bodyParser = require('body-parser');
var database = require('./database.js');
var validate = require('express-jsonschema').validate;

var cloud_services = {
  google_drive: require('./google.js'),
  dropbox: require('./dropbox.js')
}

var readDocument = database.readDocument;
var writeDocument = database.writeDocument;
var addDocument = database.addDocument;
var deleteDocument = database.deleteDocument;

var app = express();
app.use(bodyParser.text());
app.use(bodyParser.json());
app.use(express.static('../client/build'));

/**
 * Get the user ID from a token. Returns -1 (an invalid ID)
 * if it fails.
 */
function getUserIdFromToken(authorizationLine) {
  try {
    // Cut off "Bearer " from the header value.
    var token = authorizationLine.slice(7);
    // Convert the base64 string to a UTF-8 string.
    var regularString = new Buffer(token, 'base64').toString('utf8');
    // Convert the UTF-8 string into a JavaScript object.
    var tokenObj = JSON.parse(regularString);
    var id = tokenObj['id'];
    // Check that id is a number.
    if (typeof id === 'number') {
      return id;
    } else {
      // Not a number. Return -1, an invalid ID.
      return -1;
    }
  } catch (e) {
    // Return an invalid ID.
    return -1;
  }
}

// Add custom endpoints here

app.get('/user/cloudservices', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  res.send(userData.cloud_services);
});

app.get('/user/cloudservices/:service/oauth', function(req, res) {
  res.writeHead(302, {
  'Location': cloud_services[req.params.service].getOAuthURL()
  });
  res.end();
});

app.post('/user/cloudservices/:service', function(req, res) {
  var body = req.body;
  cloud_services[req.params.service].getTokenFromKey(body, token => {
    if(token == null) {
      res.status(403).end();
    } else {
      var userId = getUserIdFromToken(req.get('Authorization'));
      var userData = readDocument('users', userId);
      var addedToken = addDocument('cloud_services', token)
      userData.cloud_services[req.params.service] = addedToken._id;
      writeDocument('users', userData);
      res.send(userData.cloud_services);
    }
  });
})

app.delete('/user/cloudservices/:type', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  var cloud_services_id = userData.cloud_services[req.params.type];
  deleteDocument("cloud_services", cloud_services_id);
  delete userData.cloud_services[req.params.type];
  writeDocument('users', userData);
  res.send(userData.cloud_services);
})

app.get('/user/cloudservices/:service/files', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  if (userData.cloud_services[req.params.service] == null) {
    res.status(403).end()
  }
  var token = readDocument('cloud_services', userData.cloud_services[req.params.service]);

  var path = req.query.path;
  var cursor = req.query.cursor;

  cloud_services[req.params.service].getFiles(token, path, cursor, response => {
    if(response == null) {
      res.status(400).end();
    } else {
      res.send(response);
    }
  });
})

// Reset the database.
app.post('/resetdb', function(req, res) {
  console.log("Resetting database...");
	database.resetDatabase();
  res.send({ success: true });
});

/**
* Translate JSON Schema Validation failures into error 400s.
*/
app.use(function(err, req, res, next) {
  if (err.name === 'JsonSchemaValidation') {
    // Set a bad request http response status
    res.status(400).end();
  } else {
    // It's some other sort of error; pass it to next error middleware handler
    next(err);
  }
});

// Starts the server on port 3000!
app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});
<commit_msg>port can be now passed as program argument<commit_after>var express = require('express');
var bodyParser = require('body-parser');
var database = require('./database.js');
var validate = require('express-jsonschema').validate;

var cloud_services = {
  google_drive: require('./google.js'),
  dropbox: require('./dropbox.js')
}

var readDocument = database.readDocument;
var writeDocument = database.writeDocument;
var addDocument = database.addDocument;
var deleteDocument = database.deleteDocument;

var app = express();
app.use(bodyParser.text());
app.use(bodyParser.json());
app.use(express.static('../client/build'));

/**
 * Get the user ID from a token. Returns -1 (an invalid ID)
 * if it fails.
 */
function getUserIdFromToken(authorizationLine) {
  try {
    // Cut off "Bearer " from the header value.
    var token = authorizationLine.slice(7);
    // Convert the base64 string to a UTF-8 string.
    var regularString = new Buffer(token, 'base64').toString('utf8');
    // Convert the UTF-8 string into a JavaScript object.
    var tokenObj = JSON.parse(regularString);
    var id = tokenObj['id'];
    // Check that id is a number.
    if (typeof id === 'number') {
      return id;
    } else {
      // Not a number. Return -1, an invalid ID.
      return -1;
    }
  } catch (e) {
    // Return an invalid ID.
    return -1;
  }
}

// Add custom endpoints here

app.get('/user/cloudservices', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  res.send(userData.cloud_services);
});

app.get('/user/cloudservices/:service/oauth', function(req, res) {
  res.writeHead(302, {
  'Location': cloud_services[req.params.service].getOAuthURL()
  });
  res.end();
});

app.post('/user/cloudservices/:service', function(req, res) {
  var body = req.body;
  cloud_services[req.params.service].getTokenFromKey(body, token => {
    if(token == null) {
      res.status(403).end();
    } else {
      var userId = getUserIdFromToken(req.get('Authorization'));
      var userData = readDocument('users', userId);
      var addedToken = addDocument('cloud_services', token)
      userData.cloud_services[req.params.service] = addedToken._id;
      writeDocument('users', userData);
      res.send(userData.cloud_services);
    }
  });
})

app.delete('/user/cloudservices/:type', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  var cloud_services_id = userData.cloud_services[req.params.type];
  deleteDocument("cloud_services", cloud_services_id);
  delete userData.cloud_services[req.params.type];
  writeDocument('users', userData);
  res.send(userData.cloud_services);
})

app.get('/user/cloudservices/:service/files', function(req, res) {
  var userId = getUserIdFromToken(req.get('Authorization'));
  var userData = readDocument('users', userId);
  if (userData.cloud_services[req.params.service] == null) {
    res.status(403).end()
  }
  var token = readDocument('cloud_services', userData.cloud_services[req.params.service]);

  var path = req.query.path;
  var cursor = req.query.cursor;

  cloud_services[req.params.service].getFiles(token, path, cursor, response => {
    if(response == null) {
      res.status(400).end();
    } else {
      res.send(response);
    }
  });
})

// Reset the database.
app.post('/resetdb', function(req, res) {
  console.log("Resetting database...");
	database.resetDatabase();
  res.send({ success: true });
});

/**
* Translate JSON Schema Validation failures into error 400s.
*/
app.use(function(err, req, res, next) {
  if (err.name === 'JsonSchemaValidation') {
    // Set a bad request http response status
    res.status(400).end();
  } else {
    // It's some other sort of error; pass it to next error middleware handler
    next(err);
  }
});

// Starts the server on port specified!
console.log(process.argv);
const port = (process.argv.length > 2 && parseInt(process.argv[2]) == process.argv[2]) ? parseInt(process.argv[2]) : 3000;

app.listen(port, function () {
  console.log('Example app listening on port ' + port + "!");
});
<|endoftext|>


<commit_before>/*global $,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  var getAllMatches = function(string,regex) {
    var matches = [];
    var match;
    while (match = regex.exec(string)) {
      matches.push(match);
    }
    return matches;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFilenameLineNumberRegexSpecs = function(colour) {
    // Hard-coded for Ruby MiniTest
    if (colour === 'amber') {
      var regex = /([a-zA-Z0-9_\.]*):([0-9]*): syntax error/g;
      var slots = [1,2];
      return  [[regex,slots]];
    }
    if (colour === 'red') {
      var regex = /\[([a-zA-Z0-9_\.]*):([0-9]*)\]:/g;
      var slots = [1,2];
      return [[regex,slots]];
    }
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFileLocations = function(string,specs) {
    var result = [];
    for (var i = 0; i < specs.length; i++) {
      var spec = specs[i];
      var regex = spec[0];
      var matches =
        getAllMatches(string,regex).map(function(array) {
          var slots = spec[1];
          var filenameSlot = slots[0];
          var lineNumberSlot = slots[1];
          return [array[filenameSlot],array[lineNumberSlot]];
        });
      result = result.concat(matches);
    }
    return result;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getTopMatch = function(colour) {
    var specs = getFilenameLineNumberRegexSpecs(colour);

    var output = cd.fileContentFor('output').val();
    var matches = getFileLocations(output, specs);

    //TODO: use first filename that names a visible file
    return matches[0];
  };

  //- - - - - - - - - - - - - - - - - - - -

  var blink = function(line) {
    var oneSecond = 1000;
    // Colors are hard-wired. Saving them
    // and then resetting can fail with
    // rapid repeated Alt+g keystrokes.
    var color      = '#BFBFBF';
    var background = '#7A7A7A';

    line.css('color', background);
    line.css('background', color);
    setTimeout(function() {
      line.css('color', color);
      line.css('background', background);
    }, oneSecond);
  };

  //- - - - - - - - - - - - - - - - - - - -

  cd.scrollToError = function() {

    //return; // work in progress

    var colour = cd.currentTrafficLightColour();
    if (colour != 'amber' && colour != 'red') {
        return;
    }
    var topMatch = getTopMatch(colour);

    var filename = topMatch[0];
    var lineNumber = topMatch[1];
    var content = cd.fileContentFor(filename);
    var numbers = cd.lineNumbersFor(filename);
    var line = $('#' + lineNumber, numbers);
    var downFromTop = 150;
    var instantly = 0;

    cd.loadFile(filename);

    // line-numbers is not directly scrollable but animate works!
    numbers.animate({
      // scroll the line-number into view
      scrollTop: '+=' + (line.offset().top - downFromTop) + 'px'
      }, instantly, function() {
        // align the content to the line-number
        content.scrollTop(numbers.scrollTop());
        blink(line);
      });
  };

  return cd;
})(cyberDojo || {}, $);
<commit_msg>auto-scroll to error is still work in progress<commit_after>/*global $,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  var getAllMatches = function(string,regex) {
    var matches = [];
    var match;
    while (match = regex.exec(string)) {
      matches.push(match);
    }
    return matches;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFilenameLineNumberRegexSpecs = function(colour) {
    // Hard-coded for Ruby MiniTest
    if (colour === 'amber') {
      var regex = /([a-zA-Z0-9_\.]*):([0-9]*): syntax error/g;
      var slots = [1,2];
      return  [[regex,slots]];
    }
    if (colour === 'red') {
      var regex = /\[([a-zA-Z0-9_\.]*):([0-9]*)\]:/g;
      var slots = [1,2];
      return [[regex,slots]];
    }
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFileLocations = function(string,specs) {
    var result = [];
    for (var i = 0; i < specs.length; i++) {
      var spec = specs[i];
      var regex = spec[0];
      var matches =
        getAllMatches(string,regex).map(function(array) {
          var slots = spec[1];
          var filenameSlot = slots[0];
          var lineNumberSlot = slots[1];
          return [array[filenameSlot],array[lineNumberSlot]];
        });
      result = result.concat(matches);
    }
    return result;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getTopMatch = function(colour) {
    var specs = getFilenameLineNumberRegexSpecs(colour);

    var output = cd.fileContentFor('output').val();
    var matches = getFileLocations(output, specs);

    //TODO: use first filename that names a visible file
    return matches[0];
  };

  //- - - - - - - - - - - - - - - - - - - -

  var blink = function(line) {
    var oneSecond = 1000;
    // Colors are hard-wired. Saving them
    // and then resetting can fail with
    // rapid repeated Alt+g keystrokes.
    var color      = '#BFBFBF';
    var background = '#7A7A7A';

    line.css('color', background);
    line.css('background', color);
    setTimeout(function() {
      line.css('color', color);
      line.css('background', background);
    }, oneSecond);
  };

  //- - - - - - - - - - - - - - - - - - - -

  cd.scrollToError = function() {

    return; // work in progress

    var colour = cd.currentTrafficLightColour();
    if (colour != 'amber' && colour != 'red') {
        return;
    }
    var topMatch = getTopMatch(colour);

    var filename = topMatch[0];
    var lineNumber = topMatch[1];
    var content = cd.fileContentFor(filename);
    var numbers = cd.lineNumbersFor(filename);
    var line = $('#' + lineNumber, numbers);
    var downFromTop = 150;
    var instantly = 0;

    cd.loadFile(filename);

    // line-numbers is not directly scrollable but animate works!
    numbers.animate({
      // scroll the line-number into view
      scrollTop: '+=' + (line.offset().top - downFromTop) + 'px'
      }, instantly, function() {
        // align the content to the line-number
        content.scrollTop(numbers.scrollTop());
        blink(line);
      });
  };

  return cd;
})(cyberDojo || {}, $);
<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 120 */
/*global module, require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx Model
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Lucas Dohmen
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var Model,
  _ = require("underscore"),
  is = require("org/arangodb/is"),
  extend = require('org/arangodb/extend').extend,
  metadataKeys = ['_id', '_key', '_rev'],
  parseAttributes,
  parseRequiredAttributes;

// -----------------------------------------------------------------------------
// --SECTION--                                                             Model
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_initializer
///
/// `new FoxxModel(data)`
///
/// If you initialize a model, you can give it initial *data* as an object.
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

var whitelistProperties = function (properties, constructorProperties, whitelistMetadata) {
  'use strict';
  if (!properties) {
    return {};
  }

  if (!constructorProperties) {
    return _.clone(properties);
  }

  var whitelistedKeys = _.keys(constructorProperties);

  if (whitelistMetadata) {
    whitelistedKeys = whitelistedKeys.concat(metadataKeys);
  }

  return _.pick(properties, whitelistedKeys);
};

var fillInDefaults = function (properties, constructorProperties) {
  'use strict';
  if (!constructorProperties) {
    return properties;
  }
  var defaults = _.reduce(constructorProperties, function (result, value, key) {
    if (_.has(value, "defaultValue")) {
      result[key] = value.defaultValue;
    }

    return result;
  }, {});

  return _.defaults(properties, defaults);
};

Model = function (attributes) {
  'use strict';

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_attributes
/// @brief The attributes property is the internal hash containing the model's state.
////////////////////////////////////////////////////////////////////////////////

  this.attributes = whitelistProperties(attributes, this.constructor.attributes, true);
  this.attributes = fillInDefaults(this.attributes, this.constructor.attributes);
  this.whitelistedAttributes = whitelistProperties(this.attributes, this.constructor.attributes);
};

Model.fromClient = function (attributes) {
  'use strict';

  var instance = new this(attributes);
  instance.attributes = whitelistProperties(attributes, this.attributes, false);
  instance.attributes = fillInDefaults(instance.attributes, this.attributes);
  instance.whitelistedAttributes = whitelistProperties(instance.attributes, this.attributes);
  return instance;
};

parseAttributes = function (rawAttributes) {
  'use strict';
  var properties = {};

  _.each(rawAttributes, function (value, key) {
    if (_.isString(value)) {
      properties[key] = {
        type: value
      };
    } else {
      properties[key] = {
        type: value.type
      };
    }
  });

  return properties;
};

parseRequiredAttributes = function (rawAttributes) {
  'use strict';
  var requiredProperties = [];

  _.each(rawAttributes, function (value, key) {
    if (_.isObject(value) && value.required) {
      requiredProperties.push(key);
    }
  });

  return requiredProperties;
};

// "Class" Properties
_.extend(Model, {
  // TODO: Docs

  toJSONSchema: function (id) {
    'use strict';
    var attributes = this.attributes;

    return {
      id: id,
      required: parseRequiredAttributes(attributes),
      properties: parseAttributes(attributes)
    };
  }
});

// Instance Properties
_.extend(Model.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_get
///
/// `FoxxModel::get(name)`
///
/// Get the value of an attribute
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.get("a");
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  get: function (attributeName) {
    'use strict';
    var attrs = this.constructor.attributes;
    if (attrs && _.keys(attrs).indexOf(attributeName) === -1) {
      throw new Error("Unknown attribute: " + attributeName);
    }

    return this.attributes[attributeName];
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_set
///
/// `FoxxModel::set(name, value)`
///
/// Set the value of an attribute or multiple attributes at once
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.set("a", 2);
/// instance.set({
///   b: 2
/// });
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  set: function (attributeName, value) {
    'use strict';
    var constructorAttributes = this.constructor.attributes,
      attributes = attributeName;

    if (!is.object(attributeName)) {
      attributes = {};
      attributes[attributeName] = value;
    }

    if (constructorAttributes) {
      _.each(_.keys(attributes), function (key) {
        if (!constructorAttributes.hasOwnProperty(key)) {
          throw new Error("Unknown attribute: " + key);
        }
        var value = attributes[key],
          actualType = typeof value,
          expectedType = constructorAttributes[key].type;

        if (value === undefined || value === null) {
          return;
        }
        if (_.isArray(value)) {
          actualType = "array";
        }
        if (expectedType === "integer" && actualType === "number") {
          actualType = (Math.floor(value) === value ? "integer" : "number");
        }
        if (actualType !== expectedType) {
          throw new Error(
            "Invalid value for \"" +
              key + "\": Expected " + expectedType +
              " instead of " + actualType + "!"
          );
        }
      });
    }

    _.extend(this.attributes, attributes);
    this.whitelistedAttributes = whitelistProperties(this.attributes, constructorAttributes);
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_has
///
/// `FoxxModel::has(name)`
///
/// Returns true if the attribute is set to a non-null or non-undefined value.
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.has("a"); //=> true
/// instance.has("b"); //=> false
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  has: function (attributeName) {
    'use strict';
    return !(_.isUndefined(this.attributes[attributeName]) ||
             _.isNull(this.attributes[attributeName]));
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_forDB
///
/// `FoxxModel::forDB()`
///
/// Return a copy of the model which can be saved into ArangoDB
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  forDB: function () {
    'use strict';
    return this.attributes;
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_forClient
///
/// `FoxxModel::forClient()`
///
/// Return a copy of the model which you can send to the client.
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  forClient: function () {
    'use strict';
    return this.whitelistedAttributes;
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_extend
///
/// `FoxxModel::extend(instanceProperties, classProperties)`
///
/// Extend the Model prototype to add or overwrite methods.
/// The first object contains the properties to be defined on the instance,
/// the second object those to be defined on the prototype.
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

Model.extend = extend;

exports.Model = Model;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:
<commit_msg>Don't throw in Model::get.<commit_after>/*jslint indent: 2, nomen: true, maxlen: 120 */
/*global module, require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx Model
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Lucas Dohmen
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var Model,
  _ = require("underscore"),
  is = require("org/arangodb/is"),
  extend = require('org/arangodb/extend').extend,
  metadataKeys = ['_id', '_key', '_rev'],
  parseAttributes,
  parseRequiredAttributes;

// -----------------------------------------------------------------------------
// --SECTION--                                                             Model
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_initializer
///
/// `new FoxxModel(data)`
///
/// If you initialize a model, you can give it initial *data* as an object.
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

var whitelistProperties = function (properties, constructorProperties, whitelistMetadata) {
  'use strict';
  if (!properties) {
    return {};
  }

  if (!constructorProperties) {
    return _.clone(properties);
  }

  var whitelistedKeys = _.keys(constructorProperties);

  if (whitelistMetadata) {
    whitelistedKeys = whitelistedKeys.concat(metadataKeys);
  }

  return _.pick(properties, whitelistedKeys);
};

var fillInDefaults = function (properties, constructorProperties) {
  'use strict';
  if (!constructorProperties) {
    return properties;
  }
  var defaults = _.reduce(constructorProperties, function (result, value, key) {
    if (_.has(value, "defaultValue")) {
      result[key] = value.defaultValue;
    }

    return result;
  }, {});

  return _.defaults(properties, defaults);
};

Model = function (attributes) {
  'use strict';

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_model_attributes
/// @brief The attributes property is the internal hash containing the model's state.
////////////////////////////////////////////////////////////////////////////////

  this.attributes = whitelistProperties(attributes, this.constructor.attributes, true);
  this.attributes = fillInDefaults(this.attributes, this.constructor.attributes);
  this.whitelistedAttributes = whitelistProperties(this.attributes, this.constructor.attributes);
};

Model.fromClient = function (attributes) {
  'use strict';

  var instance = new this(attributes);
  instance.attributes = whitelistProperties(attributes, this.attributes, false);
  instance.attributes = fillInDefaults(instance.attributes, this.attributes);
  instance.whitelistedAttributes = whitelistProperties(instance.attributes, this.attributes);
  return instance;
};

parseAttributes = function (rawAttributes) {
  'use strict';
  var properties = {};

  _.each(rawAttributes, function (value, key) {
    if (_.isString(value)) {
      properties[key] = {
        type: value
      };
    } else {
      properties[key] = {
        type: value.type
      };
    }
  });

  return properties;
};

parseRequiredAttributes = function (rawAttributes) {
  'use strict';
  var requiredProperties = [];

  _.each(rawAttributes, function (value, key) {
    if (_.isObject(value) && value.required) {
      requiredProperties.push(key);
    }
  });

  return requiredProperties;
};

// "Class" Properties
_.extend(Model, {
  // TODO: Docs

  toJSONSchema: function (id) {
    'use strict';
    var attributes = this.attributes;

    return {
      id: id,
      required: parseRequiredAttributes(attributes),
      properties: parseAttributes(attributes)
    };
  }
});

// Instance Properties
_.extend(Model.prototype, {
////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_get
///
/// `FoxxModel::get(name)`
///
/// Get the value of an attribute
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.get("a");
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  get: function (attributeName) {
    'use strict';
    return this.attributes[attributeName];
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_set
///
/// `FoxxModel::set(name, value)`
///
/// Set the value of an attribute or multiple attributes at once
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.set("a", 2);
/// instance.set({
///   b: 2
/// });
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  set: function (attributeName, value) {
    'use strict';
    var constructorAttributes = this.constructor.attributes,
      attributes = attributeName;

    if (!is.object(attributeName)) {
      attributes = {};
      attributes[attributeName] = value;
    }

    if (constructorAttributes) {
      _.each(_.keys(attributes), function (key) {
        if (!constructorAttributes.hasOwnProperty(key)) {
          throw new Error("Unknown attribute: " + key);
        }
        var value = attributes[key],
          actualType = typeof value,
          expectedType = constructorAttributes[key].type;

        if (value === undefined || value === null) {
          return;
        }
        if (_.isArray(value)) {
          actualType = "array";
        }
        if (expectedType === "integer" && actualType === "number") {
          actualType = (Math.floor(value) === value ? "integer" : "number");
        }
        if (actualType !== expectedType) {
          throw new Error(
            "Invalid value for \"" +
              key + "\": Expected " + expectedType +
              " instead of " + actualType + "!"
          );
        }
      });
    }

    _.extend(this.attributes, attributes);
    this.whitelistedAttributes = whitelistProperties(this.attributes, constructorAttributes);
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_has
///
/// `FoxxModel::has(name)`
///
/// Returns true if the attribute is set to a non-null or non-undefined value.
///
/// @EXAMPLES
///
/// ```js
/// instance = new Model({
///   a: 1
/// });
///
/// instance.has("a"); //=> true
/// instance.has("b"); //=> false
/// ```
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  has: function (attributeName) {
    'use strict';
    return !(_.isUndefined(this.attributes[attributeName]) ||
             _.isNull(this.attributes[attributeName]));
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_forDB
///
/// `FoxxModel::forDB()`
///
/// Return a copy of the model which can be saved into ArangoDB
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  forDB: function () {
    'use strict';
    return this.attributes;
  },

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_forClient
///
/// `FoxxModel::forClient()`
///
/// Return a copy of the model which you can send to the client.
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

  forClient: function () {
    'use strict';
    return this.whitelistedAttributes;
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @startDocuBlock JSF_foxx_model_extend
///
/// `FoxxModel::extend(instanceProperties, classProperties)`
///
/// Extend the Model prototype to add or overwrite methods.
/// The first object contains the properties to be defined on the instance,
/// the second object those to be defined on the prototype.
/// @endDocuBlock
////////////////////////////////////////////////////////////////////////////////

Model.extend = extend;

exports.Model = Model;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:
<|endoftext|>


<commit_before>import PostCooked from 'discourse/widgets/post-cooked';
import { createWidget } from 'discourse/widgets/widget';
import { iconNode } from 'discourse/helpers/fa-icon';
import { transformBasicPost } from 'discourse/lib/transform-post';
import { h } from 'virtual-dom';
import DiscourseURL from 'discourse/lib/url';
import { dateNode } from 'discourse/helpers/node';

export function avatarImg(wanted, attrs) {
  const size = Discourse.Utilities.translateSize(wanted);
  const url = Discourse.Utilities.avatarUrl(attrs.template, size);

  // We won't render an invalid url
  if (!url || url.length === 0) { return; }
  const title = attrs.username;

  const properties = {
    attributes: { alt: '', width: size, height: size, src: Discourse.getURLWithCDN(url), title },
    className: 'avatar'
  };

  return h('img', properties);
}

export function avatarFor(wanted, attrs) {
  return h('a', {
    className: `trigger-user-card ${attrs.className || ''}`,
    attributes: { href: attrs.url, 'data-user-card': attrs.username }
  }, avatarImg(wanted, attrs));
}

createWidget('select-post', {
  tagName: 'div.select-posts',

  html(attrs) {
    const buttons = [];

    if (attrs.replyCount > 0 && !attrs.selected) {
      buttons.push(this.attach('button', { label: 'topic.multi_select.select_replies', action: 'selectReplies' }));
    }

    const selectPostKey = attrs.selected ? 'topic.multi_select.selected' : 'topic.multi_select.select';
    buttons.push(this.attach('button', { className: 'select-post',
                                         label: selectPostKey,
                                         labelOptions: { count: attrs.selectedPostsCount },
                                         action: 'selectPost' }));
    return buttons;
  }
});

createWidget('reply-to-tab', {
  tagName: 'a.reply-to-tab',

  defaultState() {
    return { loading: false };
  },

  html(attrs, state) {
    if (state.loading) { return I18n.t('loading'); }

    return [iconNode('mail-forward'),
            ' ',
            avatarFor.call(this, 'small', {
              template: attrs.replyToAvatarTemplate,
              username: attrs.replyToUsername
            }),
            ' ',
            h('span', attrs.replyToUsername)];
  },

  click() {
    this.state.loading = true;
    this.sendWidgetAction('toggleReplyAbove').then(() => this.state.loading = false);
  }
});

createWidget('post-avatar', {
  tagName: 'div.topic-avatar',

  html(attrs) {
    let body;
    if (!attrs.user_id) {
      body = h('i', { className: 'fa fa-trash-o deleted-user-avatar' });
    } else {
      body = avatarFor.call(this, 'large', {
        template: attrs.avatar_template,
        username: attrs.username,
        url: attrs.usernameUrl,
        className: 'main-avatar'
      });
    }

    return [body, h('div.poster-avatar-extra')];
  }
});

createWidget('wiki-edit-button', {
  tagName: 'div.post-info.wiki',
  title: 'post.wiki.about',

  html() {
    return iconNode('pencil-square-o');
  },

  click() {
    this.sendWidgetAction('editPost');
  }
});

createWidget('post-email-indicator', {
  tagName: 'div.post-info.via-email',
  title: 'post.via_email',

  buildClasses(attrs) {
    return attrs.canViewRawEmail ? 'raw-email' : null;
  },

  html() {
    return iconNode('envelope-o');
  },

  click() {
    if (this.attrs.canViewRawEmail) {
      this.sendWidgetAction('showRawEmail');
    }
  }
});

function showReplyTab(attrs, siteSettings) {
  return attrs.replyToUsername &&
         (!attrs.replyDirectlyAbove || !siteSettings.suppress_reply_directly_above);
}

createWidget('post-meta-data', {
  tagName: 'div.topic-meta-data',
  html(attrs) {
    const result = [this.attach('poster-name', attrs)];

    if (attrs.isWhisper) {
      result.push(h('div.post-info.whisper', {
        attributes: { title: I18n.t('post.whisper') },
      }, iconNode('eye-slash')));
    }

    const createdAt = new Date(attrs.created_at);
    if (createdAt) {
      result.push(h('div.post-info',
        h('a.post-date', {
          attributes: {
            href: attrs.shareUrl,
            'data-share-url': attrs.shareUrl,
            'data-post-number': attrs.post_number
          }
        }, dateNode(createdAt))
      ));
    }

    if (attrs.via_email) {
      result.push(this.attach('post-email-indicator', attrs));
    }

    if (attrs.version > 1) {
      result.push(this.attach('post-edits-indicator', attrs));
    }

    if (attrs.wiki) {
      result.push(this.attach('wiki-edit-button', attrs));
    }

    if (attrs.multiSelect) {
      result.push(this.attach('select-post', attrs));
    }

    if (showReplyTab(attrs, this.siteSettings)) {
      result.push(this.attach('reply-to-tab', attrs));
    }

    result.push(h('div.read-state', {
      className: attrs.read ? 'read' : null,
      attributes: {
        title: I18n.t('post.unread')
      }
    }, iconNode('circle')));

    return result;
  }
});

createWidget('expand-hidden', {
  tagName: 'a.expand-hidden',

  html() {
    return I18n.t('post.show_hidden');
  },

  click() {
    this.sendWidgetAction('expandHidden');
  }
});

createWidget('expand-post-button', {
  tagName: 'button.btn.expand-post',
  buildKey: attrs => `expand-post-button-${attrs.id}`,

  defaultState() {
    return { loadingExpanded: false };
  },

  html(attrs, state) {
    if (state.loadingExpanded) {
      return I18n.t('loading');
    } else {
      return [I18n.t('post.show_full'), "..."];
    }
  },

  click() {
    this.state.loadingExpanded = true;
    this.sendWidgetAction('expandFirstPost');
  }
});

createWidget('post-contents', {
  buildKey: attrs => `post-contents-${attrs.id}`,

  defaultState() {
    return { expandedFirstPost: false, repliesBelow: [] };
  },

  buildClasses(attrs) {
    const classes = ['regular'];
    if (!this.state.repliesShown) {
      classes.push('contents');
    }
    if (showReplyTab(attrs, this.siteSettings)) {
      classes.push('avoid-tab');
    }
    return classes;
  },

  html(attrs, state) {
    const result = [new PostCooked(attrs)];

    if (attrs.cooked_hidden) {
      result.push(this.attach('expand-hidden', attrs));
    }

    if (!state.expandedFirstPost && attrs.expandablePost) {
      result.push(this.attach('expand-post-button', attrs));
    }

    const extraState = { state: { repliesShown: !!state.repliesBelow.length } };
    result.push(this.attach('post-menu', attrs, extraState));

    const repliesBelow = state.repliesBelow;
    if (repliesBelow.length) {
      result.push(h('section.embedded-posts.bottom', repliesBelow.map(p => this.attach('embedded-post', p))));
    }

    return result;
  },

  toggleRepliesBelow() {
    if (this.state.repliesBelow.length) {
      this.state.repliesBelow = [];
      return;
    }

    return this.store.find('post-reply', { postId: this.attrs.id }).then(posts => {
      this.state.repliesBelow = posts.map(transformBasicPost);
    });
  },

  expandFirstPost() {
    const post = this.findAncestorModel();
    return post.expand().then(() => this.state.expandedFirstPost = true);
  }
});

createWidget('post-body', {
  tagName: 'div.topic-body',

  html(attrs) {
    const postContents = this.attach('post-contents', attrs);
    const result = [this.attach('post-meta-data', attrs), postContents];

    result.push(this.attach('actions-summary', attrs));
    if (attrs.showTopicMap) {
      result.push(this.attach('topic-map', attrs));
    }

    return result;
  }
});

createWidget('post-article', {
  tagName: 'article.boxed.onscreen-post',
  buildKey: attrs => `post-article-${attrs.id}`,

  defaultState() {
    return { repliesAbove: [] };
  },

  buildId(attrs) {
    return `post_${attrs.post_number}`;
  },

  buildClasses(attrs) {
    if (attrs.via_email) { return 'via-email'; }
  },

  buildAttributes(attrs) {
    return { 'data-post-id': attrs.id, 'data-user-id': attrs.user_id };
  },

  html(attrs, state) {
    const rows = [h('a.tabLoc')];
    if (state.repliesAbove.length) {
      const replies = state.repliesAbove.map(p => this.attach('embedded-post', p, { state: { above: true } }));
      rows.push(h('div.row', h('section.embedded-posts.top.topic-body.offset2', replies)));
    }

    rows.push(h('div.row', [this.attach('post-avatar', attrs),
                            this.attach('post-body', attrs),
                            this.attach('post-gutter', attrs)]));
    return rows;
  },

  toggleReplyAbove() {
    const replyPostNumber = this.attrs.reply_to_post_number;

    // jump directly on mobile
    if (Discourse.Mobile.mobileView) {
      DiscourseURL.jumpToPost(replyPostNumber);
      return Ember.RSVP.Promise.resolve();
    }

    if (this.state.repliesAbove.length) {
      this.state.repliesAbove = [];
      return Ember.RSVP.Promise.resolve();
    } else {
      return this.store.find('post-reply-history', { postId: this.attrs.id }).then(posts => {
        this.state.repliesAbove = posts.map(transformBasicPost);
      });
    }
  },

});

export default createWidget('post', {
  buildKey: attrs => `post-${attrs.id}`,
  shadowTree: true,

  buildClasses(attrs) {
    const classNames = ['topic-post', 'clearfix'];

    if (attrs.selected) { classNames.push('selected'); }
    if (attrs.topicOwner) { classNames.push('topic-owner'); }
    if (attrs.hidden) { classNames.push('post-hidden'); }
    if (attrs.deleted) { classNames.push('deleted'); }
    if (attrs.primary_group_name) { classNames.push(`group-${attrs.primary_group_name}`); }
    if (attrs.wiki) { classNames.push(`wiki`); }
    if (attrs.isWhisper) { classNames.push('whisper'); }
    if (attrs.isModeratorAction || (attrs.isWarning && attrs.firstPost)) {
      classNames.push('moderator');
    } else {
      classNames.push('regular');
    }
    return classNames;
  },

  html(attrs) {
    return this.attach('post-article', attrs);
  },

  toggleLike() {
    const post = this.model;
    const likeAction = post.get('likeAction');

    if (likeAction && likeAction.get('canToggle')) {
      return likeAction.togglePromise(post);
    }
  },

  undoPostAction(typeId) {
    const post = this.model;
    return post.get('actions_summary').findProperty('id', typeId).undo(post);
  },

  deferPostActionFlags(typeId) {
    const post = this.model;
    return post.get('actions_summary').findProperty('id', typeId).deferFlags(post);
  }
});
<commit_msg>FIX: Tab was broken with keyboard shortcuts<commit_after>import PostCooked from 'discourse/widgets/post-cooked';
import { createWidget } from 'discourse/widgets/widget';
import { iconNode } from 'discourse/helpers/fa-icon';
import { transformBasicPost } from 'discourse/lib/transform-post';
import { h } from 'virtual-dom';
import DiscourseURL from 'discourse/lib/url';
import { dateNode } from 'discourse/helpers/node';

export function avatarImg(wanted, attrs) {
  const size = Discourse.Utilities.translateSize(wanted);
  const url = Discourse.Utilities.avatarUrl(attrs.template, size);

  // We won't render an invalid url
  if (!url || url.length === 0) { return; }
  const title = attrs.username;

  const properties = {
    attributes: { alt: '', width: size, height: size, src: Discourse.getURLWithCDN(url), title },
    className: 'avatar'
  };

  return h('img', properties);
}

export function avatarFor(wanted, attrs) {
  return h('a', {
    className: `trigger-user-card ${attrs.className || ''}`,
    attributes: { href: attrs.url, 'data-user-card': attrs.username }
  }, avatarImg(wanted, attrs));
}

createWidget('select-post', {
  tagName: 'div.select-posts',

  html(attrs) {
    const buttons = [];

    if (attrs.replyCount > 0 && !attrs.selected) {
      buttons.push(this.attach('button', { label: 'topic.multi_select.select_replies', action: 'selectReplies' }));
    }

    const selectPostKey = attrs.selected ? 'topic.multi_select.selected' : 'topic.multi_select.select';
    buttons.push(this.attach('button', { className: 'select-post',
                                         label: selectPostKey,
                                         labelOptions: { count: attrs.selectedPostsCount },
                                         action: 'selectPost' }));
    return buttons;
  }
});

createWidget('reply-to-tab', {
  tagName: 'a.reply-to-tab',

  defaultState() {
    return { loading: false };
  },

  html(attrs, state) {
    if (state.loading) { return I18n.t('loading'); }

    return [iconNode('mail-forward'),
            ' ',
            avatarFor.call(this, 'small', {
              template: attrs.replyToAvatarTemplate,
              username: attrs.replyToUsername
            }),
            ' ',
            h('span', attrs.replyToUsername)];
  },

  click() {
    this.state.loading = true;
    this.sendWidgetAction('toggleReplyAbove').then(() => this.state.loading = false);
  }
});

createWidget('post-avatar', {
  tagName: 'div.topic-avatar',

  html(attrs) {
    let body;
    if (!attrs.user_id) {
      body = h('i', { className: 'fa fa-trash-o deleted-user-avatar' });
    } else {
      body = avatarFor.call(this, 'large', {
        template: attrs.avatar_template,
        username: attrs.username,
        url: attrs.usernameUrl,
        className: 'main-avatar'
      });
    }

    return [body, h('div.poster-avatar-extra')];
  }
});

createWidget('wiki-edit-button', {
  tagName: 'div.post-info.wiki',
  title: 'post.wiki.about',

  html() {
    return iconNode('pencil-square-o');
  },

  click() {
    this.sendWidgetAction('editPost');
  }
});

createWidget('post-email-indicator', {
  tagName: 'div.post-info.via-email',
  title: 'post.via_email',

  buildClasses(attrs) {
    return attrs.canViewRawEmail ? 'raw-email' : null;
  },

  html() {
    return iconNode('envelope-o');
  },

  click() {
    if (this.attrs.canViewRawEmail) {
      this.sendWidgetAction('showRawEmail');
    }
  }
});

function showReplyTab(attrs, siteSettings) {
  return attrs.replyToUsername &&
         (!attrs.replyDirectlyAbove || !siteSettings.suppress_reply_directly_above);
}

createWidget('post-meta-data', {
  tagName: 'div.topic-meta-data',
  html(attrs) {
    const result = [this.attach('poster-name', attrs)];

    if (attrs.isWhisper) {
      result.push(h('div.post-info.whisper', {
        attributes: { title: I18n.t('post.whisper') },
      }, iconNode('eye-slash')));
    }

    const createdAt = new Date(attrs.created_at);
    if (createdAt) {
      result.push(h('div.post-info',
        h('a.post-date', {
          attributes: {
            href: attrs.shareUrl,
            'data-share-url': attrs.shareUrl,
            'data-post-number': attrs.post_number
          }
        }, dateNode(createdAt))
      ));
    }

    if (attrs.via_email) {
      result.push(this.attach('post-email-indicator', attrs));
    }

    if (attrs.version > 1) {
      result.push(this.attach('post-edits-indicator', attrs));
    }

    if (attrs.wiki) {
      result.push(this.attach('wiki-edit-button', attrs));
    }

    if (attrs.multiSelect) {
      result.push(this.attach('select-post', attrs));
    }

    if (showReplyTab(attrs, this.siteSettings)) {
      result.push(this.attach('reply-to-tab', attrs));
    }

    result.push(h('div.read-state', {
      className: attrs.read ? 'read' : null,
      attributes: {
        title: I18n.t('post.unread')
      }
    }, iconNode('circle')));

    return result;
  }
});

createWidget('expand-hidden', {
  tagName: 'a.expand-hidden',

  html() {
    return I18n.t('post.show_hidden');
  },

  click() {
    this.sendWidgetAction('expandHidden');
  }
});

createWidget('expand-post-button', {
  tagName: 'button.btn.expand-post',
  buildKey: attrs => `expand-post-button-${attrs.id}`,

  defaultState() {
    return { loadingExpanded: false };
  },

  html(attrs, state) {
    if (state.loadingExpanded) {
      return I18n.t('loading');
    } else {
      return [I18n.t('post.show_full'), "..."];
    }
  },

  click() {
    this.state.loadingExpanded = true;
    this.sendWidgetAction('expandFirstPost');
  }
});

createWidget('post-contents', {
  buildKey: attrs => `post-contents-${attrs.id}`,

  defaultState() {
    return { expandedFirstPost: false, repliesBelow: [] };
  },

  buildClasses(attrs) {
    const classes = ['regular'];
    if (!this.state.repliesShown) {
      classes.push('contents');
    }
    if (showReplyTab(attrs, this.siteSettings)) {
      classes.push('avoid-tab');
    }
    return classes;
  },

  html(attrs, state) {
    const result = [new PostCooked(attrs)];

    if (attrs.cooked_hidden) {
      result.push(this.attach('expand-hidden', attrs));
    }

    if (!state.expandedFirstPost && attrs.expandablePost) {
      result.push(this.attach('expand-post-button', attrs));
    }

    const extraState = { state: { repliesShown: !!state.repliesBelow.length } };
    result.push(this.attach('post-menu', attrs, extraState));

    const repliesBelow = state.repliesBelow;
    if (repliesBelow.length) {
      result.push(h('section.embedded-posts.bottom', repliesBelow.map(p => this.attach('embedded-post', p))));
    }

    return result;
  },

  toggleRepliesBelow() {
    if (this.state.repliesBelow.length) {
      this.state.repliesBelow = [];
      return;
    }

    return this.store.find('post-reply', { postId: this.attrs.id }).then(posts => {
      this.state.repliesBelow = posts.map(transformBasicPost);
    });
  },

  expandFirstPost() {
    const post = this.findAncestorModel();
    return post.expand().then(() => this.state.expandedFirstPost = true);
  }
});

createWidget('post-body', {
  tagName: 'div.topic-body',

  html(attrs) {
    const postContents = this.attach('post-contents', attrs);
    const result = [this.attach('post-meta-data', attrs), postContents];

    result.push(this.attach('actions-summary', attrs));
    if (attrs.showTopicMap) {
      result.push(this.attach('topic-map', attrs));
    }

    return result;
  }
});

createWidget('post-article', {
  tagName: 'article.boxed.onscreen-post',
  buildKey: attrs => `post-article-${attrs.id}`,

  defaultState() {
    return { repliesAbove: [] };
  },

  buildId(attrs) {
    return `post_${attrs.post_number}`;
  },

  buildClasses(attrs) {
    if (attrs.via_email) { return 'via-email'; }
  },

  buildAttributes(attrs) {
    return { 'data-post-id': attrs.id, 'data-user-id': attrs.user_id };
  },

  html(attrs, state) {
    const rows = [h('a.tabLoc', { attributes: { href: ''} })];
    if (state.repliesAbove.length) {
      const replies = state.repliesAbove.map(p => this.attach('embedded-post', p, { state: { above: true } }));
      rows.push(h('div.row', h('section.embedded-posts.top.topic-body.offset2', replies)));
    }

    rows.push(h('div.row', [this.attach('post-avatar', attrs),
                            this.attach('post-body', attrs),
                            this.attach('post-gutter', attrs)]));
    return rows;
  },

  toggleReplyAbove() {
    const replyPostNumber = this.attrs.reply_to_post_number;

    // jump directly on mobile
    if (Discourse.Mobile.mobileView) {
      DiscourseURL.jumpToPost(replyPostNumber);
      return Ember.RSVP.Promise.resolve();
    }

    if (this.state.repliesAbove.length) {
      this.state.repliesAbove = [];
      return Ember.RSVP.Promise.resolve();
    } else {
      return this.store.find('post-reply-history', { postId: this.attrs.id }).then(posts => {
        this.state.repliesAbove = posts.map(transformBasicPost);
      });
    }
  },

});

export default createWidget('post', {
  buildKey: attrs => `post-${attrs.id}`,
  shadowTree: true,

  buildClasses(attrs) {
    const classNames = ['topic-post', 'clearfix'];

    if (attrs.selected) { classNames.push('selected'); }
    if (attrs.topicOwner) { classNames.push('topic-owner'); }
    if (attrs.hidden) { classNames.push('post-hidden'); }
    if (attrs.deleted) { classNames.push('deleted'); }
    if (attrs.primary_group_name) { classNames.push(`group-${attrs.primary_group_name}`); }
    if (attrs.wiki) { classNames.push(`wiki`); }
    if (attrs.isWhisper) { classNames.push('whisper'); }
    if (attrs.isModeratorAction || (attrs.isWarning && attrs.firstPost)) {
      classNames.push('moderator');
    } else {
      classNames.push('regular');
    }
    return classNames;
  },

  html(attrs) {
    return this.attach('post-article', attrs);
  },

  toggleLike() {
    const post = this.model;
    const likeAction = post.get('likeAction');

    if (likeAction && likeAction.get('canToggle')) {
      return likeAction.togglePromise(post);
    }
  },

  undoPostAction(typeId) {
    const post = this.model;
    return post.get('actions_summary').findProperty('id', typeId).undo(post);
  },

  deferPostActionFlags(typeId) {
    const post = this.model;
    return post.get('actions_summary').findProperty('id', typeId).deferFlags(post);
  }
});
<|endoftext|>