<commit_before>/**
 * Copyright (c) 2017 ~ present NAVER Corp.
 * billboard.js project is licensed under the MIT license
 */
import ChartInternal from "./ChartInternal";
import {extend, brushEmpty, getBrushSelection, getMinMax, isDefined, notEmpty, isValue, isObject, isNumber, diffDomain, sortValue} from "./util";

extend(ChartInternal.prototype, {
	getYDomainMinMax(targets, type) {
		const $$ = this;
		const config = $$.config;
		const isMin = type === "min";

		const dataGroups = config.data_groups;
		const ids = $$.mapToIds(targets);
		const ys = $$.getValuesAsIdKeyed(targets);

		if (dataGroups.length > 0) {
			const hasValue = $$[`has${isMin ? "Negative" : "Positive"}ValueInTargets`](targets);

			for (let j = 0, idsInGroup; (idsInGroup = dataGroups[j]); j++) {
				// Determine baseId
				idsInGroup = idsInGroup.filter(v => ids.indexOf(v) >= 0);

				if (idsInGroup.length === 0) {
					continue;
				}

				const baseId = idsInGroup[0];
				const baseAxisId = $$.axis.getId(baseId);

				// Initialize base value. Set to 0 if not match with the condition
				if (hasValue && ys[baseId]) {
					ys[baseId] = ys[baseId].map(v => (
						(isMin ? v < 0 : v > 0) ? v : 0
					));
				}

				for (let k = 1, id; (id = idsInGroup[k]); k++) {
					if (!ys[id]) {
						continue;
					}

					const axisId = $$.axis.getId(id);

					ys[id].forEach((v, i) => {
						const val = +v;
						const meetCondition = isMin ? val > 0 : val < 0;

						if (axisId === baseAxisId && !(hasValue && meetCondition)) {
							ys[baseId][i] += val;
						}
					});
				}
			}
		}

		return getMinMax(type, Object.keys(ys).map(key => getMinMax(type, ys[key])));
	},

	getYDomainMin(targets) {
		return this.getYDomainMinMax(targets, "min");
	},

	getYDomainMax(targets) {
		return this.getYDomainMinMax(targets, "max");
	},

	getYDomain(targets, axisId, xDomain) {
		const $$ = this;
		const config = $$.config;

		if ($$.isStackNormalized()) {
			return [0, 100];
		}

		const targetsByAxisId = targets.filter(t => $$.axis.getId(t.id) === axisId);
		const yTargets = xDomain ? $$.filterByXDomain(targetsByAxisId, xDomain) : targetsByAxisId;
		const yMin = axisId === "y2" ? config.axis_y2_min : config.axis_y_min;
		const yMax = axisId === "y2" ? config.axis_y2_max : config.axis_y_max;
		let yDomainMin = $$.getYDomainMin(yTargets);
		let yDomainMax = $$.getYDomainMax(yTargets);
		const center = axisId === "y2" ? config.axis_y2_center : config.axis_y_center;
		let isZeroBased = ($$.hasType("bar", yTargets) && config.bar_zerobased) || ($$.hasType("area", yTargets) && config.area_zerobased);
		const isInverted = axisId === "y2" ? config.axis_y2_inverted : config.axis_y_inverted;
		const showHorizontalDataLabel = $$.hasDataLabel() && config.axis_rotated;
		const showVerticalDataLabel = $$.hasDataLabel() && !config.axis_rotated;
		let lengths;

		// MEMO: avoid inverting domain unexpectedly
		yDomainMin = isValue(yMin) ? yMin :
			(isValue(yMax) ? (yDomainMin < yMax ? yDomainMin : yMax - 10) : yDomainMin);
		yDomainMax = isValue(yMax) ? yMax :
			(isValue(yMin) ? (yMin < yDomainMax ? yDomainMax : yMin + 10) : yDomainMax);

		if (yTargets.length === 0) { // use current domain if target of axisId is none
			return axisId === "y2" ? $$.y2.domain() : $$.y.domain();
		}

		if (isNaN(yDomainMin)) { // set minimum to zero when not number
			yDomainMin = 0;
		}

		if (isNaN(yDomainMax)) { // set maximum to have same value as yDomainMin
			yDomainMax = yDomainMin;
		}

		if (yDomainMin === yDomainMax) {
			yDomainMin < 0 ? yDomainMax = 0 : yDomainMin = 0;
		}

		const isAllPositive = yDomainMin >= 0 && yDomainMax >= 0;
		const isAllNegative = yDomainMin <= 0 && yDomainMax <= 0;

		// Cancel zerobased if axis_*_min / axis_*_max specified
		if ((isValue(yMin) && isAllPositive) || (isValue(yMax) && isAllNegative)) {
			isZeroBased = false;
		}

		// Bar/Area chart should be 0-based if all positive|negative
		if (isZeroBased) {
			isAllPositive && (yDomainMin = 0);
			isAllNegative && (yDomainMax = 0);
		}

		const domainLength = Math.abs(yDomainMax - yDomainMin);
		let paddingTop = domainLength * 0.1;
		let paddingBottom = domainLength * 0.1;

		if (isDefined(center)) {
			const yDomainAbs = Math.max(Math.abs(yDomainMin), Math.abs(yDomainMax));

			yDomainMax = center + yDomainAbs;
			yDomainMin = center - yDomainAbs;
		}

		// add padding for data label
		if (showHorizontalDataLabel) {
			lengths = $$.getDataLabelLength(yDomainMin, yDomainMax, "width");
			const diff = diffDomain($$.y.range());
			const ratio = [lengths[0] / diff, lengths[1] / diff];

			paddingTop += domainLength * (ratio[1] / (1 - ratio[0] - ratio[1]));
			paddingBottom += domainLength * (ratio[0] / (1 - ratio[0] - ratio[1]));
		} else if (showVerticalDataLabel) {
			lengths = $$.getDataLabelLength(yDomainMin, yDomainMax, "height");
			paddingTop += $$.axis.convertPixelsToAxisPadding(lengths[1], domainLength);
			paddingBottom += $$.axis.convertPixelsToAxisPadding(lengths[0], domainLength);
		}

		if (axisId === "y" && notEmpty(config.axis_y_padding)) {
			paddingTop = $$.axis.getPadding(config.axis_y_padding, "top", paddingTop, domainLength);
			paddingBottom = $$.axis.getPadding(config.axis_y_padding, "bottom", paddingBottom, domainLength);
		}

		if (axisId === "y2" && notEmpty(config.axis_y2_padding)) {
			paddingTop = $$.axis.getPadding(config.axis_y2_padding, "top", paddingTop, domainLength);
			paddingBottom = $$.axis.getPadding(config.axis_y2_padding, "bottom", paddingBottom, domainLength);
		}

		// Bar/Area chart should be 0-based if all positive|negative
		if (isZeroBased) {
			isAllPositive && (paddingBottom = yDomainMin);
			isAllNegative && (paddingTop = -yDomainMax);
		}

		const domain = [yDomainMin - paddingBottom, yDomainMax + paddingTop];

		return isInverted ? domain.reverse() : domain;
	},

	getXDomainMinMax(targets, type) {
		const $$ = this;
		const value = $$.config[`axis_x_${type}`];

		return isDefined(value) ?
			($$.isTimeSeries() ? $$.parseDate(value) : value) :
			getMinMax(type, targets.map(t => getMinMax(type, t.values.map(v => v.x))));
	},

	getXDomainMin(targets) {
		return this.getXDomainMinMax(targets, "min");
	},

	getXDomainMax(targets) {
		return this.getXDomainMinMax(targets, "max");
	},

	getXDomainPadding(domain) {
		const $$ = this;
		const config = $$.config;
		const diff = domain[1] - domain[0];
		const xPadding = config.axis_x_padding;
		let maxDataCount;
		let padding;

		if ($$.isCategorized()) {
			padding = 0;
		} else if ($$.hasType("bar")) {
			maxDataCount = $$.getMaxDataCount();
			padding = maxDataCount > 1 ? (diff / (maxDataCount - 1)) / 2 : 0.5;
		} else {
			padding = diff * 0.01;
		}

		let left = padding;
		let right = padding;

		if (isObject(xPadding) && notEmpty(xPadding)) {
			left = isValue(xPadding.left) ? xPadding.left : padding;
			right = isValue(xPadding.right) ? xPadding.right : padding;
		} else if (isNumber(config.axis_x_padding)) {
			left = xPadding;
			right = xPadding;
		}

		return {left, right};
	},

	getXDomain(targets) {
		const $$ = this;
		const xDomain = [$$.getXDomainMin(targets), $$.getXDomainMax(targets)];
		let firstX = xDomain[0];
		let lastX = xDomain[1];
		const padding = $$.getXDomainPadding(xDomain);
		let min = 0;
		let max = 0;
		// show center of x domain if min and max are the same

		if ((firstX - lastX) === 0 && !$$.isCategorized()) {
			if ($$.isTimeSeries()) {
				firstX = new Date(firstX.getTime() * 0.5);
				lastX = new Date(lastX.getTime() * 1.5);
			} else {
				firstX = firstX === 0 ? 1 : (firstX * 0.5);
				lastX = lastX === 0 ? -1 : (lastX * 1.5);
			}
		}

		if (firstX || firstX === 0) {
			min = $$.isTimeSeries() ? new Date(firstX.getTime() - padding.left) : firstX - padding.left;
		}

		if (lastX || lastX === 0) {
			max = $$.isTimeSeries() ? new Date(lastX.getTime() + padding.right) : lastX + padding.right;
		}

		return [min, max];
	},

	updateXDomain(targets, withUpdateXDomain, withUpdateOrgXDomain, withTrim, domain) {
		const $$ = this;
		const config = $$.config;
		const zoomEnabled = config.zoom_enabled;

		if (withUpdateOrgXDomain) {
			$$.x.domain(domain || sortValue($$.getXDomain(targets)));
			$$.orgXDomain = $$.x.domain();

			zoomEnabled && $$.zoom.updateScaleExtent();

			$$.subX.domain($$.x.domain());
			$$.brush && $$.brush.scale($$.subX);
		}

		if (withUpdateXDomain) {
			const domainValue = domain || (!$$.brush || brushEmpty($$)) ?
				$$.orgXDomain : getBrushSelection($$).map($$.subX.invert);

			$$.x.domain(domainValue);
			zoomEnabled && $$.zoom.updateScaleExtent();
		}

		// Trim domain when too big by zoom mousemove event
		withTrim && $$.x.domain($$.trimXDomain($$.x.orgDomain()));

		return $$.x.domain();
	},

	trimXDomain(domain) {
		const zoomDomain = this.getZoomDomain();
		const min = zoomDomain[0];
		const max = zoomDomain[1];

		if (domain[0] <= min) {
			domain[1] = +domain[1] + (min - domain[0]);
			domain[0] = min;
		}

		if (max <= domain[1]) {
			domain[0] = +domain[0] - (domain[1] - max);
			domain[1] = max;
		}

		return domain;
	},
});
<commit_msg>refactor(domain): some refactoring (#889)<commit_after>/**
 * Copyright (c) 2017 ~ present NAVER Corp.
 * billboard.js project is licensed under the MIT license
 */
import ChartInternal from "./ChartInternal";
import {extend, brushEmpty, getBrushSelection, getMinMax, isDefined, notEmpty, isValue, isObject, isNumber, diffDomain, sortValue} from "./util";

extend(ChartInternal.prototype, {
	getYDomainMinMax(targets, type) {
		const $$ = this;
		const config = $$.config;
		const isMin = type === "min";

		const dataGroups = config.data_groups;
		const ids = $$.mapToIds(targets);
		const ys = $$.getValuesAsIdKeyed(targets);

		if (dataGroups.length > 0) {
			const hasValue = $$[`has${isMin ? "Negative" : "Positive"}ValueInTargets`](targets);

			for (let j = 0, idsInGroup; (idsInGroup = dataGroups[j]); j++) {
				// Determine baseId
				idsInGroup = idsInGroup.filter(v => ids.indexOf(v) >= 0);

				if (idsInGroup.length === 0) {
					continue;
				}

				const baseId = idsInGroup[0];
				const baseAxisId = $$.axis.getId(baseId);

				// Initialize base value. Set to 0 if not match with the condition
				if (hasValue && ys[baseId]) {
					ys[baseId] = ys[baseId].map(v => (
						(isMin ? v < 0 : v > 0) ? v : 0
					));
				}

				for (let k = 1, id; (id = idsInGroup[k]); k++) {
					if (!ys[id]) {
						continue;
					}

					const axisId = $$.axis.getId(id);

					ys[id].forEach((v, i) => {
						const val = +v;
						const meetCondition = isMin ? val > 0 : val < 0;

						if (axisId === baseAxisId && !(hasValue && meetCondition)) {
							ys[baseId][i] += val;
						}
					});
				}
			}
		}

		return getMinMax(type, Object.keys(ys).map(key => getMinMax(type, ys[key])));
	},

	getYDomainMin(targets) {
		return this.getYDomainMinMax(targets, "min");
	},

	getYDomainMax(targets) {
		return this.getYDomainMinMax(targets, "max");
	},

	getYDomain(targets, axisId, xDomain) {
		const $$ = this;
		const config = $$.config;

		if ($$.isStackNormalized()) {
			return [0, 100];
		}

		const targetsByAxisId = targets.filter(t => $$.axis.getId(t.id) === axisId);
		const yTargets = xDomain ? $$.filterByXDomain(targetsByAxisId, xDomain) : targetsByAxisId;
		const yMin = config[`axis_${axisId}_min`];
		const yMax = config[`axis_${axisId}_max`];
		let yDomainMin = $$.getYDomainMin(yTargets);
		let yDomainMax = $$.getYDomainMax(yTargets);

		const center = config[`axis_${axisId}_center`];
		let isZeroBased = ($$.hasType("bar", yTargets) && config.bar_zerobased) || ($$.hasType("area", yTargets) && config.area_zerobased);
		const isInverted = config[`axis_${axisId}_inverted`];
		const showHorizontalDataLabel = $$.hasDataLabel() && config.axis_rotated;
		const showVerticalDataLabel = $$.hasDataLabel() && !config.axis_rotated;

		// MEMO: avoid inverting domain unexpectedly
		yDomainMin = isValue(yMin) ? yMin :
			(isValue(yMax) ? (yDomainMin < yMax ? yDomainMin : yMax - 10) : yDomainMin);
		yDomainMax = isValue(yMax) ? yMax :
			(isValue(yMin) ? (yMin < yDomainMax ? yDomainMax : yMin + 10) : yDomainMax);

		if (yTargets.length === 0) { // use current domain if target of axisId is none
			return $$[axisId].domain();
		}

		if (isNaN(yDomainMin)) { // set minimum to zero when not number
			yDomainMin = 0;
		}

		if (isNaN(yDomainMax)) { // set maximum to have same value as yDomainMin
			yDomainMax = yDomainMin;
		}

		if (yDomainMin === yDomainMax) {
			yDomainMin < 0 ? yDomainMax = 0 : yDomainMin = 0;
		}

		const isAllPositive = yDomainMin >= 0 && yDomainMax >= 0;
		const isAllNegative = yDomainMin <= 0 && yDomainMax <= 0;

		// Cancel zerobased if axis_*_min / axis_*_max specified
		if ((isValue(yMin) && isAllPositive) || (isValue(yMax) && isAllNegative)) {
			isZeroBased = false;
		}

		// Bar/Area chart should be 0-based if all positive|negative
		if (isZeroBased) {
			isAllPositive && (yDomainMin = 0);
			isAllNegative && (yDomainMax = 0);
		}

		const domainLength = Math.abs(yDomainMax - yDomainMin);
		const padding = {top: domainLength * 0.1, bottom: domainLength * 0.1};

		if (isDefined(center)) {
			const yDomainAbs = Math.max(Math.abs(yDomainMin), Math.abs(yDomainMax));

			yDomainMax = center + yDomainAbs;
			yDomainMin = center - yDomainAbs;
		}

		// add padding for data label
		if (showHorizontalDataLabel) {
			const diff = diffDomain($$.y.range());
			const ratio = $$.getDataLabelLength(yDomainMin, yDomainMax, "width")
				.map(v => v / diff);

			["bottom", "top"].forEach((v, i) => {
				padding[v] += domainLength * (ratio[i] / (1 - ratio[0] - ratio[1]));
			});
		} else if (showVerticalDataLabel) {
			const lengths = $$.getDataLabelLength(yDomainMin, yDomainMax, "height");

			["bottom", "top"].forEach((v, i) => {
				padding[v] += $$.axis.convertPixelsToAxisPadding(lengths[i], domainLength);
			});
		}

		if (/^y2?$/.test(axisId)) {
			const p = config[`axis_${axisId}_padding`];

			if (notEmpty(p)) {
				["bottom", "top"].forEach(v => {
					padding[v] = $$.axis.getPadding(p, v, padding[v], domainLength);
				});
			}
		}

		// Bar/Area chart should be 0-based if all positive|negative
		if (isZeroBased) {
			isAllPositive && (padding.bottom = yDomainMin);
			isAllNegative && (padding.top = -yDomainMax);
		}

		const domain = [yDomainMin - padding.bottom, yDomainMax + padding.top];

		return isInverted ? domain.reverse() : domain;
	},

	getXDomainMinMax(targets, type) {
		const $$ = this;
		const value = $$.config[`axis_x_${type}`];

		return isDefined(value) ?
			($$.isTimeSeries() ? $$.parseDate(value) : value) :
			getMinMax(type, targets.map(t => getMinMax(type, t.values.map(v => v.x))));
	},

	getXDomainMin(targets) {
		return this.getXDomainMinMax(targets, "min");
	},

	getXDomainMax(targets) {
		return this.getXDomainMinMax(targets, "max");
	},

	getXDomainPadding(domain) {
		const $$ = this;
		const config = $$.config;
		const diff = domain[1] - domain[0];
		const xPadding = config.axis_x_padding;
		let maxDataCount;
		let padding;

		if ($$.isCategorized()) {
			padding = 0;
		} else if ($$.hasType("bar")) {
			maxDataCount = $$.getMaxDataCount();
			padding = maxDataCount > 1 ? (diff / (maxDataCount - 1)) / 2 : 0.5;
		} else {
			padding = diff * 0.01;
		}

		let left = padding;
		let right = padding;

		if (isObject(xPadding) && notEmpty(xPadding)) {
			left = isValue(xPadding.left) ? xPadding.left : padding;
			right = isValue(xPadding.right) ? xPadding.right : padding;
		} else if (isNumber(config.axis_x_padding)) {
			left = xPadding;
			right = xPadding;
		}

		return {left, right};
	},

	getXDomain(targets) {
		const $$ = this;
		const xDomain = [$$.getXDomainMin(targets), $$.getXDomainMax(targets)];
		let firstX = xDomain[0];
		let lastX = xDomain[1];
		const padding = $$.getXDomainPadding(xDomain);
		let min = 0;
		let max = 0;

		// show center of x domain if min and max are the same
		if ((firstX - lastX) === 0 && !$$.isCategorized()) {
			if ($$.isTimeSeries()) {
				firstX = new Date(firstX.getTime() * 0.5);
				lastX = new Date(lastX.getTime() * 1.5);
			} else {
				firstX = firstX === 0 ? 1 : (firstX * 0.5);
				lastX = lastX === 0 ? -1 : (lastX * 1.5);
			}
		}

		if (firstX || firstX === 0) {
			min = $$.isTimeSeries() ? new Date(firstX.getTime() - padding.left) : firstX - padding.left;
		}

		if (lastX || lastX === 0) {
			max = $$.isTimeSeries() ? new Date(lastX.getTime() + padding.right) : lastX + padding.right;
		}

		return [min, max];
	},

	updateXDomain(targets, withUpdateXDomain, withUpdateOrgXDomain, withTrim, domain) {
		const $$ = this;
		const config = $$.config;
		const zoomEnabled = config.zoom_enabled;

		if (withUpdateOrgXDomain) {
			$$.x.domain(domain || sortValue($$.getXDomain(targets)));
			$$.orgXDomain = $$.x.domain();

			zoomEnabled && $$.zoom.updateScaleExtent();

			$$.subX.domain($$.x.domain());
			$$.brush && $$.brush.scale($$.subX);
		}

		if (withUpdateXDomain) {
			const domainValue = domain || (!$$.brush || brushEmpty($$)) ?
				$$.orgXDomain : getBrushSelection($$).map($$.subX.invert);

			$$.x.domain(domainValue);
			zoomEnabled && $$.zoom.updateScaleExtent();
		}

		// Trim domain when too big by zoom mousemove event
		withTrim && $$.x.domain($$.trimXDomain($$.x.orgDomain()));

		return $$.x.domain();
	},

	trimXDomain(domain) {
		const zoomDomain = this.getZoomDomain();
		const [min, max] = zoomDomain;

		if (domain[0] <= min) {
			domain[1] = +domain[1] + (min - domain[0]);
			domain[0] = min;
		}

		if (max <= domain[1]) {
			domain[0] = +domain[0] - (domain[1] - max);
			domain[1] = max;
		}

		return domain;
	},
});
<|endoftext|>


<commit_before>import Autocomplete from './mixins/autocomplete'
import Filterable from '~mixins/filterable'
import Generators from './mixins/generators'
import Input from '~mixins/input'

export default {
  name: 'select',

  mixins: [Autocomplete, Input, Filterable, Generators],

  data () {
    return {
      content: {},
      inputValue: this.multiple && !this.value ? [] : this.value,
      isBooted: false,
      lastItem: 20,
      isActive: false
    }
  },

  props: {
    appendIcon: {
      type: String,
      default: 'arrow_drop_down'
    },
    auto: Boolean,
    autocomplete: Boolean,
    bottom: Boolean,
    chips: Boolean,
    close: Boolean,
    debounce: {
      type: Number,
      default: 200
    },
    items: {
      type: Array,
      default: () => []
    },
    filter: Function,
    itemText: {
      type: String,
      default: 'text'
    },
    itemValue: {
      type: String,
      default: 'value'
    },
    maxHeight: {
      type: [Number, String],
      default: 300
    },
    multiple: Boolean,
    multiLine: Boolean,
    offset: Boolean,
    singleLine: Boolean,
    top: Boolean,
    returnObject: Boolean,
    overflow: Boolean,
    segmented: Boolean,
    editable: Boolean
  },

  computed: {
    classes () {
      return {
        'input-group--text-field input-group--select': true,
        'input-group--auto': this.auto,
        'input-group--overflow': this.overflow,
        'input-group--segmented': this.segmented,
        'input-group--editable': this.editable,
        'input-group--autocomplete': this.autocomplete,
        'input-group--single-line': this.singleLine || this.isDropdown,
        'input-group--multi-line': this.multiLine,
        'input-group--chips': this.chips,
        'input-group--multiple': this.multiple
      }
    },
    computedContentClass () {
      const children = [
        this.auto ? 'menu__content--auto' : '',
        this.isDropdown ? 'menu__content--dropdown' : ''
      ]

      return children.join(' ')
    },
    filteredItems () {
      const items = this.autocomplete && this.searchValue
        ? this.filterSearch()
        : this.items

      return !this.auto ? items.slice(0, this.lastItem) : items
    },
    isDirty () {
      return this.selectedItems.length
    },
    isDropdown () {
      return this.segmented || this.overflow || this.editable
    },
    selectedItems () {
      if (this.inputValue === null || typeof this.inputValue === 'undefined') return []

      return this.items.filter(i => {
        if (!this.multiple) {
          return this.getValue(i) === this.getValue(this.inputValue)
        } else {
          // Always return Boolean
          return this.inputValue.find(j => this.getValue(j) === this.getValue(i)) !== undefined
        }
      })
    }
  },

  watch: {
    inputValue (val) {
      this.$emit('input', val)
    },
    value (val) {
      this.inputValue = val
      this.validate()
      if (this.autocomplete || this.editable) this.$nextTick(this.$refs.menu.updateDimensions)
    },
    isActive (val) {
      this.isBooted = true
      this.lastItem += !val ? 20 : 0

      if (!val) this.blur()
      else this.focus()
    },
    isBooted () {
      this.$nextTick(() => {
        this.content && this.content.addEventListener('scroll', this.onScroll, false)
      })
    },
    searchValue () {
      this.$refs.menu.listIndex = -1
    }
  },

  mounted () {
    this.content = this.$refs.menu.$refs.content
  },

  beforeDestroy () {
    if (this.isBooted) {
      this.content && this.content.removeEventListener('scroll', this.onScroll, false)
    }
  },

  methods: {
    blur () {
      this.$nextTick(() => {
        this.focused = false
        this.searchValue = null
        this.$el.blur()
      })
    },
    focus () {
      this.focused = true
      this.$refs.input &&
        (this.autocomplete || this.editable) &&
        this.$refs.input.focus()

      if (this.editable &&
          this.inputValue !== null &&
          typeof this.inputValue !== 'undefined'
        ) {
        this.$nextTick(() => (this.$refs.input.value = this.getValue(this.inputValue)))
      }
    },
    genLabel () {
      if (this.searchValue) return null

      const data = {}

      if (this.id) data.attrs = { for: this.id }

      return this.$createElement('label', data, this.label)
    },
    getText (item) {
      return item === Object(item) ? item[this.itemText] : item
    },
    getValue (item) {
      return item === Object(item) && (this.itemValue in item) ? item[this.itemValue] : item
    },
    onScroll () {
      if (!this.isActive) {
        requestAnimationFrame(() => (this.content.scrollTop = 0))
      } else {
        const showMoreItems = (
          this.content.scrollHeight -
          (this.content.scrollTop +
          this.content.clientHeight)
        ) < 200

        if (showMoreItems) {
          this.lastItem += 20
        }
      }
    },
    selectItem (item) {
      if (!this.multiple) {
        this.inputValue = this.returnObject ? item : this.getValue(item)
      } else {
        const inputValue = this.inputValue.slice()
        const i = this.inputValue.findIndex(i => this.getValue(i) === this.getValue(item))

        i !== -1 && inputValue.splice(i, 1) || inputValue.push(item)
        this.inputValue = inputValue.map(i => this.returnObject ? i : this.getValue(i))
      }

      if (this.autocomplete) {
        this.$nextTick(() => {
          this.searchValue = null
          this.$refs.input &&
            this.$refs.input.focus()
        })
      }

      this.$emit('change', this.inputValue)
    }
  },

  render (h) {
    return this.genInputGroup([
      this.genSelectionsAndSearch(),
      this.genMenu()
    ], {
      ref: 'activator',
      directives: [{
        name: 'click-outside',
        value: () => (this.isActive = false)
      }],
      on: {
        keydown: e => this.$refs.menu.changeListIndex(e)
      }
    })
  }
}
<commit_msg>fixed bug where select item-text and item-value props did not handle dot notation (#985)<commit_after>import Autocomplete from './mixins/autocomplete'
import Filterable from '~mixins/filterable'
import Generators from './mixins/generators'
import Input from '~mixins/input'
import { getObjectValueByPath } from '~util/helpers'

export default {
  name: 'select',

  mixins: [Autocomplete, Input, Filterable, Generators],

  data () {
    return {
      content: {},
      inputValue: this.multiple && !this.value ? [] : this.value,
      isBooted: false,
      lastItem: 20,
      isActive: false
    }
  },

  props: {
    appendIcon: {
      type: String,
      default: 'arrow_drop_down'
    },
    auto: Boolean,
    autocomplete: Boolean,
    bottom: Boolean,
    chips: Boolean,
    close: Boolean,
    debounce: {
      type: Number,
      default: 200
    },
    items: {
      type: Array,
      default: () => []
    },
    filter: Function,
    itemText: {
      type: String,
      default: 'text'
    },
    itemValue: {
      type: String,
      default: 'value'
    },
    maxHeight: {
      type: [Number, String],
      default: 300
    },
    multiple: Boolean,
    multiLine: Boolean,
    offset: Boolean,
    singleLine: Boolean,
    top: Boolean,
    returnObject: Boolean,
    overflow: Boolean,
    segmented: Boolean,
    editable: Boolean
  },

  computed: {
    classes () {
      return {
        'input-group--text-field input-group--select': true,
        'input-group--auto': this.auto,
        'input-group--overflow': this.overflow,
        'input-group--segmented': this.segmented,
        'input-group--editable': this.editable,
        'input-group--autocomplete': this.autocomplete,
        'input-group--single-line': this.singleLine || this.isDropdown,
        'input-group--multi-line': this.multiLine,
        'input-group--chips': this.chips,
        'input-group--multiple': this.multiple
      }
    },
    computedContentClass () {
      const children = [
        this.auto ? 'menu__content--auto' : '',
        this.isDropdown ? 'menu__content--dropdown' : ''
      ]

      return children.join(' ')
    },
    filteredItems () {
      const items = this.autocomplete && this.searchValue
        ? this.filterSearch()
        : this.items

      return !this.auto ? items.slice(0, this.lastItem) : items
    },
    isDirty () {
      return this.selectedItems.length
    },
    isDropdown () {
      return this.segmented || this.overflow || this.editable
    },
    selectedItems () {
      if (this.inputValue === null || typeof this.inputValue === 'undefined') return []

      return this.items.filter(i => {
        if (!this.multiple) {
          return this.getValue(i) === this.getValue(this.inputValue)
        } else {
          // Always return Boolean
          return this.inputValue.find(j => this.getValue(j) === this.getValue(i)) !== undefined
        }
      })
    }
  },

  watch: {
    inputValue (val) {
      this.$emit('input', val)
    },
    value (val) {
      this.inputValue = val
      this.validate()
      if (this.autocomplete || this.editable) this.$nextTick(this.$refs.menu.updateDimensions)
    },
    isActive (val) {
      this.isBooted = true
      this.lastItem += !val ? 20 : 0

      if (!val) this.blur()
      else this.focus()
    },
    isBooted () {
      this.$nextTick(() => {
        this.content && this.content.addEventListener('scroll', this.onScroll, false)
      })
    },
    searchValue () {
      this.$refs.menu.listIndex = -1
    }
  },

  mounted () {
    this.content = this.$refs.menu.$refs.content
  },

  beforeDestroy () {
    if (this.isBooted) {
      this.content && this.content.removeEventListener('scroll', this.onScroll, false)
    }
  },

  methods: {
    blur () {
      this.$nextTick(() => {
        this.focused = false
        this.searchValue = null
        this.$el.blur()
      })
    },
    focus () {
      this.focused = true
      this.$refs.input &&
        (this.autocomplete || this.editable) &&
        this.$refs.input.focus()

      if (this.editable &&
          this.inputValue !== null &&
          typeof this.inputValue !== 'undefined'
        ) {
        this.$nextTick(() => (this.$refs.input.value = this.getValue(this.inputValue)))
      }
    },
    genLabel () {
      if (this.searchValue) return null

      const data = {}

      if (this.id) data.attrs = { for: this.id }

      return this.$createElement('label', data, this.label)
    },
    getText (item) {
      return item === Object(item) ? getObjectValueByPath(item, this.itemText) : item
    },
    getValue (item) {
      return item === Object(item) ? getObjectValueByPath(item, this.itemValue) : item
    },
    onScroll () {
      if (!this.isActive) {
        requestAnimationFrame(() => (this.content.scrollTop = 0))
      } else {
        const showMoreItems = (
          this.content.scrollHeight -
          (this.content.scrollTop +
          this.content.clientHeight)
        ) < 200

        if (showMoreItems) {
          this.lastItem += 20
        }
      }
    },
    selectItem (item) {
      if (!this.multiple) {
        this.inputValue = this.returnObject ? item : this.getValue(item)
      } else {
        const inputValue = this.inputValue.slice()
        const i = this.inputValue.findIndex(i => this.getValue(i) === this.getValue(item))

        i !== -1 && inputValue.splice(i, 1) || inputValue.push(item)
        this.inputValue = inputValue.map(i => this.returnObject ? i : this.getValue(i))
      }

      if (this.autocomplete) {
        this.$nextTick(() => {
          this.searchValue = null
          this.$refs.input &&
            this.$refs.input.focus()
        })
      }

      this.$emit('change', this.inputValue)
    }
  },

  render (h) {
    return this.genInputGroup([
      this.genSelectionsAndSearch(),
      this.genMenu()
    ], {
      ref: 'activator',
      directives: [{
        name: 'click-outside',
        value: () => (this.isActive = false)
      }],
      on: {
        keydown: e => this.$refs.menu.changeListIndex(e)
      }
    })
  }
}
<|endoftext|>


<commit_before>window.initModifyQueryComponent = function(modifyQueryButtonId, querySelectorId) {
    var selectorInitialized = false;
    var selectorVisible = false;

    setTimeout(function() {
        $("#" + modifyQueryButtonId).show();
    }, 2000);

    $("#" + modifyQueryButtonId).click(function() {
        if (!selectorInitialized) {
            window.renderQuerySelector(document.getElementById(querySelectorId));
            selectorInitialized = true;
        }

        // toggle visibility
        selectorVisible = !selectorVisible;

        if (selectorVisible) {
            $("#" + querySelectorId).slideDown();
        }
        else {
            $("#" + querySelectorId).slideUp();
        }
    });
};
<commit_msg>We don't want to show download tab in query selector when in modify-query scenario (results page)<commit_after>window.initModifyQueryComponent = function(modifyQueryButtonId, querySelectorId) {
    var selectorInitialized = false;
    var selectorVisible = false;

    setTimeout(function() {
        $("#" + modifyQueryButtonId).show();
    }, 2000);

    $("#" + modifyQueryButtonId).click(function() {
        if (!selectorInitialized) {
            window.renderQuerySelector(document.getElementById(querySelectorId), {showDownloadTab:false});
            selectorInitialized = true;
        }

        // toggle visibility
        selectorVisible = !selectorVisible;

        if (selectorVisible) {
            $("#" + querySelectorId).slideDown();
        }
        else {
            $("#" + querySelectorId).slideUp();
        }
    });
};
<|endoftext|>


<commit_before>var request = require("request");
var dom = require("xmldom").DOMParser;
var xpath = require("xpath");

function GithubPlugin(bot) {
	var self = this;
	self.name = "github";
	self.help = "Github stats plugin";
	self.depend = ["cmd", "bits"];

	self.blocks = "▁▂▃▄▅▆▇█";

	self.userRe = /^\w[\w-]+$/;
	self.repoRe = /^(\w[\w-]+)\/(\w[\w-]+)$/;

	self.token = null;
	self.request = null;

	self.users = {};

	self.setToken = function(token) {
		if (token) {
			self.token = token;
			self.request = request.defaults({auth: {username: self.token + ":x-oauth-basic"}, headers: {"User-Agent": "simbot GitHub"}});
		}
		else {
			self.token = null;
			self.request = request.defaults({headers: {"User-Agent": "simbot GitHub"}});
		}
	};

	self.load = function(data) {
		if (data && data.users)
			self.users = data.users;

		self.setToken(data.token);
	};

	self.save = function() {
		return {
			"token": self.token,
			"users": self.users
		};
	};

	self.parseuser = function(user) {
		if (user in self.users)
			return self.users[user];
		else
			return user;
	};

	self.graph = function(arr, minMult) {
		var min = Math.min.apply(null, arr);
		var max = Math.max.apply(null, arr);
		var mult = Math.max(minMult || 0.5, (max - min) / (self.blocks.length - 1));

		var str = "";
		for (var i = 0; i < arr.length; i++) {
			var x = (arr[i] - min) / mult;
			str += self.blocks[Math.ceil(x)];
		}
		return str;
	};

	self.events = {
		"cmd#github": function(nick, to, args) {
			var realarg = args[1] || nick;
			var arg = self.parseuser(realarg.toLowerCase());

			var prefix = "";
			var bits = [];

			var output = function() {
				bot.say(to, bot.plugins.bits.format(prefix, bits));
			};

			if (arg.match(self.repoRe)) { // repo
				self.request("https://api.github.com/repos/" + arg, function(err, res, body) {
					if (!err && res.statusCode == 200) {
						var j = JSON.parse(body);
						prefix = j.full_name;
						if (j.description)
							bits.push([, j.description, 0]);
						bits.push(["stars", j.stargazers_count]);
						bits.push(["watch", j.watchers_count]);
						bits.push(["forks", j.forks_count]);
						if (j.language)
							bits.push(["language", j.language]);
						bits.push(["issues", j.open_issues_count]);
						bits.push([, j.html_url, 2]);

						output();
					}
					else
						bot.out.error("github", err, body);
				});
			}
			else if (arg.match(self.userRe)) { // user/org
				self.request("https://api.github.com/users/" + arg, function(err, res, body) {
					if (!err && res.statusCode == 200) {
						var j = JSON.parse(body);
						prefix = j.login + (realarg.toLowerCase() != j.login.toLowerCase() ? " (" + realarg + ")" : "");
						bits.push([, j.type]);
						if (j.bio)
							bits.push([, j.bio, 0]);
						bits.push(["repos", j.public_repos]);
						bits.push(["followers", j.followers]);
						bits.push(["following", j.following]);

						var finish = function() {
							bits.push([, j.html_url, 2]);
							output();
						};

						if (j.type == "User") {
							self.request("https://github.com/users/" + j.login + "/contributions", function(err, res, body) {
								if (!err && res.statusCode == 200) {
									var doc = new dom().parseFromString(body);
									var nodes = xpath.select("//rect[@class='day']", doc);
									var contribs = [];
									for (var i = 0; i < nodes.length; i++) {
										var date = nodes[i].getAttribute("data-date");
										var count = nodes[i].getAttribute("data-count");
										contribs.push([date, parseInt(count), 0]);
									}
									// TODO: guarantee contribs sorted by date

									var longstreak = 0;
									var total = contribs[0][1];
									var most = 0;
									for (var i = 1; i < contribs.length; i++) {
										total += contribs[i][1];

										if (contribs[i][1] > 0) {
											if (contribs[i - 1][1] > 0)
												contribs[i][2] = contribs[i - 1][2] + 1;
											else
												contribs[i][2] = 1;
										}

										longstreak = Math.max(longstreak, contribs[i][2]);
										most = Math.max(most, contribs[i][1]);
									}
									var curstreak = Math.max(contribs[contribs.length - 2][2], contribs[contribs.length - 1][2]);

									bits.push(["contributions", total]);
									bits.push(["most daily contributions", most]);
									bits.push(["longest streak", longstreak + " days"]);
									bits.push(["current streak", curstreak + " days"]);

									var justContribs = contribs.map(function(tuple) {
										return tuple[1];
									});
									bits.push(["recent contributions", self.graph(justContribs.slice(-14)), 0]);

									finish();
								}
								else
									bot.out.error("github", err, body);
							});
						}
						else
							finish();
					}
					else
						bot.out.error("github", err, body);
				});
			}
			else {
				bot.say(to, nick + ": invalid argument '\x02" + arg + "\x02'");
			}
		},

		"cmd#setgithub": function(nick, to, args) {
			bot.plugins.nickserv.nickIdentified(nick, function(identified) {
				if (identified) {
					if (args[1] !== undefined) {
						self.users[nick.toLowerCase()] = args[1];
						bot.notice(nick, "github set to " + args[1]);
					}
					else {
						delete self.users[nick.toLowerCase()];
						bot.notice(nick, "github unset");
					}
				}
				else
					bot.notice(nick, "must be identified for this nick to set github");
			});
		}
	};
}

module.exports = GithubPlugin;
<commit_msg>github: fix not counting first day into streak<commit_after>var request = require("request");
var dom = require("xmldom").DOMParser;
var xpath = require("xpath");

function GithubPlugin(bot) {
	var self = this;
	self.name = "github";
	self.help = "Github stats plugin";
	self.depend = ["cmd", "bits"];

	self.blocks = "▁▂▃▄▅▆▇█";

	self.userRe = /^\w[\w-]+$/;
	self.repoRe = /^(\w[\w-]+)\/(\w[\w-]+)$/;

	self.token = null;
	self.request = null;

	self.users = {};

	self.setToken = function(token) {
		if (token) {
			self.token = token;
			self.request = request.defaults({auth: {username: self.token + ":x-oauth-basic"}, headers: {"User-Agent": "simbot GitHub"}});
		}
		else {
			self.token = null;
			self.request = request.defaults({headers: {"User-Agent": "simbot GitHub"}});
		}
	};

	self.load = function(data) {
		if (data && data.users)
			self.users = data.users;

		self.setToken(data.token);
	};

	self.save = function() {
		return {
			"token": self.token,
			"users": self.users
		};
	};

	self.parseuser = function(user) {
		if (user in self.users)
			return self.users[user];
		else
			return user;
	};

	self.graph = function(arr, minMult) {
		var min = Math.min.apply(null, arr);
		var max = Math.max.apply(null, arr);
		var mult = Math.max(minMult || 0.5, (max - min) / (self.blocks.length - 1));

		var str = "";
		for (var i = 0; i < arr.length; i++) {
			var x = (arr[i] - min) / mult;
			str += self.blocks[Math.ceil(x)];
		}
		return str;
	};

	self.events = {
		"cmd#github": function(nick, to, args) {
			var realarg = args[1] || nick;
			var arg = self.parseuser(realarg.toLowerCase());

			var prefix = "";
			var bits = [];

			var output = function() {
				bot.say(to, bot.plugins.bits.format(prefix, bits));
			};

			if (arg.match(self.repoRe)) { // repo
				self.request("https://api.github.com/repos/" + arg, function(err, res, body) {
					if (!err && res.statusCode == 200) {
						var j = JSON.parse(body);
						prefix = j.full_name;
						if (j.description)
							bits.push([, j.description, 0]);
						bits.push(["stars", j.stargazers_count]);
						bits.push(["watch", j.watchers_count]);
						bits.push(["forks", j.forks_count]);
						if (j.language)
							bits.push(["language", j.language]);
						bits.push(["issues", j.open_issues_count]);
						bits.push([, j.html_url, 2]);

						output();
					}
					else
						bot.out.error("github", err, body);
				});
			}
			else if (arg.match(self.userRe)) { // user/org
				self.request("https://api.github.com/users/" + arg, function(err, res, body) {
					if (!err && res.statusCode == 200) {
						var j = JSON.parse(body);
						prefix = j.login + (realarg.toLowerCase() != j.login.toLowerCase() ? " (" + realarg + ")" : "");
						bits.push([, j.type]);
						if (j.bio)
							bits.push([, j.bio, 0]);
						bits.push(["repos", j.public_repos]);
						bits.push(["followers", j.followers]);
						bits.push(["following", j.following]);

						var finish = function() {
							bits.push([, j.html_url, 2]);
							output();
						};

						if (j.type == "User") {
							self.request("https://github.com/users/" + j.login + "/contributions", function(err, res, body) {
								if (!err && res.statusCode == 200) {
									var doc = new dom().parseFromString(body);
									var nodes = xpath.select("//rect[@class='day']", doc);
									var contribs = [];
									for (var i = 0; i < nodes.length; i++) {
										var date = nodes[i].getAttribute("data-date");
										var count = nodes[i].getAttribute("data-count");
										contribs.push([date, parseInt(count), 0]);
									}
									// TODO: guarantee contribs sorted by date

									var longstreak = 0;
									var total = 0;
									var most = 0;
									for (var i = 0; i < contribs.length; i++) {
										total += contribs[i][1];

										if (contribs[i][1] > 0) {
											if (i > 0 && contribs[i - 1][1] > 0)
												contribs[i][2] = contribs[i - 1][2] + 1;
											else
												contribs[i][2] = 1;
										}

										longstreak = Math.max(longstreak, contribs[i][2]);
										most = Math.max(most, contribs[i][1]);
									}
									var curstreak = Math.max(contribs[contribs.length - 2][2], contribs[contribs.length - 1][2]);

									bits.push(["contributions", total]);
									bits.push(["most daily contributions", most]);
									bits.push(["longest streak", longstreak + " days"]);
									bits.push(["current streak", curstreak + " days"]);

									var justContribs = contribs.map(function(tuple) {
										return tuple[1];
									});
									bits.push(["recent contributions", self.graph(justContribs.slice(-14)), 0]);

									finish();
								}
								else
									bot.out.error("github", err, body);
							});
						}
						else
							finish();
					}
					else
						bot.out.error("github", err, body);
				});
			}
			else {
				bot.say(to, nick + ": invalid argument '\x02" + arg + "\x02'");
			}
		},

		"cmd#setgithub": function(nick, to, args) {
			bot.plugins.nickserv.nickIdentified(nick, function(identified) {
				if (identified) {
					if (args[1] !== undefined) {
						self.users[nick.toLowerCase()] = args[1];
						bot.notice(nick, "github set to " + args[1]);
					}
					else {
						delete self.users[nick.toLowerCase()];
						bot.notice(nick, "github unset");
					}
				}
				else
					bot.notice(nick, "must be identified for this nick to set github");
			});
		}
	};
}

module.exports = GithubPlugin;
<|endoftext|>


<commit_before>define(['ModernizrProto', 'createElement'], function( ModernizrProto, createElement ) {
  // isEventSupported determines if a given element supports the given event
  // kangax.github.com/iseventsupported/
  //
  // The following results are known incorrects:
  //   Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
  //   Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
  var isEventSupported = (function (undefined) {

    var TAGNAMES = {
      'select': 'input', 'change': 'input',
      'submit': 'form', 'reset': 'form',
      'error': 'img', 'load': 'img', 'abort': 'img'
    },

    // Detect whether event support can be detected via `in`. Test on a DOM element
    // using the "blur" event b/c it should always exist. bit.ly/event-detection
    needsFallback = !('onblur' in document.documentElement);

    /**
     * @param  {string|*}           eventName  is the name of an event to test for (e.g. "resize")
     * @param  {(Object|string|*)=} element    is a DOM element|document|window|tagName to test on
     * @return {boolean}
     */
    function isEventSupportedInner( eventName, element ) {

      var isSupported;
      if ( !eventName ) { return false; }
      if ( !element || typeof element == 'string' ) {
        element = createElement(element || TAGNAMES[eventName] || 'div');
      } else if ( typeof element != 'object' ) {
        return false; // `element` was invalid type
      }

      // Testing via the `in` operator is sufficient for modern browsers and IE.
      // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and
      // "resize", whereas `in` "catches" those.
      eventName = 'on' + eventName;
      isSupported = eventName in element;

      // Fallback technique for old Firefox - bit.ly/event-detection
      if ( !isSupported && needsFallback ) {
        if ( !element.setAttribute ) {
          // Switch to generic element if it lacks `setAttribute`.
          // It could be the `document`, `window`, or something else.
          element = createElement('div');
        }
        if ( element.setAttribute && element.removeAttribute ) {
          element.setAttribute(eventName, '');
          isSupported = typeof element[eventName] == 'function';

          if ( element[eventName] !== undefined ) {
            // If property was created, "remove it" by setting value to `undefined`.
            element[eventName] = undefined;
          }
          element.removeAttribute(eventName);
        }
      }

      element = null; // Nullify to prevent memory leak.
      return isSupported;
    }
    return isEventSupportedInner;
  })();

  return isEventSupported;
});
<commit_msg>3qualize `typeof` for good measure.<commit_after>define(['ModernizrProto', 'createElement'], function( ModernizrProto, createElement ) {
  // isEventSupported determines if a given element supports the given event
  // kangax.github.com/iseventsupported/
  //
  // The following results are known incorrects:
  //   Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
  //   Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
  var isEventSupported = (function (undefined) {

    var TAGNAMES = {
      'select': 'input', 'change': 'input',
      'submit': 'form', 'reset': 'form',
      'error': 'img', 'load': 'img', 'abort': 'img'
    },

    // Detect whether event support can be detected via `in`. Test on a DOM element
    // using the "blur" event b/c it should always exist. bit.ly/event-detection
    needsFallback = !('onblur' in document.documentElement);

    /**
     * @param  {string|*}           eventName  is the name of an event to test for (e.g. "resize")
     * @param  {(Object|string|*)=} element    is a DOM element|document|window|tagName to test on
     * @return {boolean}
     */
    function isEventSupportedInner( eventName, element ) {

      var isSupported;
      if ( !eventName ) { return false; }
      if ( !element || typeof element === 'string' ) {
        element = createElement(element || TAGNAMES[eventName] || 'div');
      } else if ( typeof element !== 'object' ) {
        return false; // `element` was invalid type
      }

      // Testing via the `in` operator is sufficient for modern browsers and IE.
      // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and
      // "resize", whereas `in` "catches" those.
      eventName = 'on' + eventName;
      isSupported = eventName in element;

      // Fallback technique for old Firefox - bit.ly/event-detection
      if ( !isSupported && needsFallback ) {
        if ( !element.setAttribute ) {
          // Switch to generic element if it lacks `setAttribute`.
          // It could be the `document`, `window`, or something else.
          element = createElement('div');
        }
        if ( element.setAttribute && element.removeAttribute ) {
          element.setAttribute(eventName, '');
          isSupported = typeof element[eventName] === 'function';

          if ( element[eventName] !== undefined ) {
            // If property was created, "remove it" by setting value to `undefined`.
            element[eventName] = undefined;
          }
          element.removeAttribute(eventName);
        }
      }

      element = null; // Nullify to prevent memory leak.
      return isSupported;
    }
    return isEventSupportedInner;
  })();

  return isEventSupported;
});
<|endoftext|>


<commit_before>var app = angular.module("over_ez", []);
app.controller("DigestController", function($scope, $http) {
    $scope.ENTITY_EVENT_SPECIAL = 3420;
    $scope.ENTITY_EVENT_SERVICE = 3421;
    $scope.ENTITY_EVENT_FELLOWSHIP = 3422;
    $scope.ENTITY_COMMITTEE = 3423;

    $scope.daysOfWeek = [
        { value: 0, label: "...the next Sunday" },
        { value: 1, label: "...the next Monday" },
        { value: 2, label: "...the next Tuesday" },
        { value: 3, label: "...the next Wednesday" },
        { value: 4, label: "...the next Thursday" },
        { value: 5, label: "...the next Friday" },
        { value: 6, label: "...the next Saturday" }
    ]

    $scope.page = {
        showAllInOpen: false
    }

    /*
     * Get a list of digests saved to the server.
     */
    $scope.getList = function() {
        $http.get("/list")
        .then(
            function(response){ // success
                $scope.server_digest_list = response.data;
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.log("couldn't get saved digests from server");
            }
        );
    }

    $scope.getList();

    /*
     * Get the default digest configuration from the server.
     */
    $scope.getDefault = function() {
        $http.get("/get")
        .then(
            function(response){ // success
                $scope.digest = $scope.loadDigestDates(response.data);
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.error("couldn't get default digest information from server, using local hardcoded defaults")
                $scope.digest = {
                    "date" : null,
                    "cssFile" : "digest.css",
                    "message" : {
                        "enable" : false,
                        "body" : "",
                        "signoff1" : "In LFS,",
                        "signoff2" : ""
                    },
                    "events" : {
                        "special" : [],
                        "service" : [],
                        "fellowship" : []
                    },
                    "maintainer" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "svp" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "fvp" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "committees" : [],
                    "meetingMinutes" : [],
                    "meetingsUrl" : ""
                }
            }
        );
    }

    /*
     * HTML date and time fields attempt to convert the entered date or
     * time into the local time zone. As the dates and times that are
     * entered will always be in Eastern Time (for RPI's campus), it's
     * not necessary to support multiple time zones. By adding the
     * current local time offset to UTC when loading digest information
     * and subtracting it when saving digest information, we ensure that
     * users see dates and times in Eastern Time wherever they are.
     *
     * obj: digest object to convert the dates of
     */
    $scope.loadDigestDates = function(obj) {
        var userTimezoneOffset = new Date().getTimezoneOffset() * 60000;
        obj.date = obj.date ? new Date(new Date(obj.date).getTime() + userTimezoneOffset) : null;
        var digestEventGroupKeys = Object.keys(obj.events);
        for (var i = 0; i < digestEventGroupKeys.length; i++) {
            var eventGroup = obj.events[digestEventGroupKeys[i]];
            for (var j = 0; j < eventGroup.length; j++) {
                eventGroup[j].date_start = eventGroup[j].date_start ? new Date(new Date(eventGroup[j].date_start).getTime() + userTimezoneOffset) : null;
                eventGroup[j].date_end = eventGroup[j].date_end ? new Date(new Date(eventGroup[j].date_end).getTime() + userTimezoneOffset) : null;
                eventGroup[j].time_start = eventGroup[j].time_start ? new Date(new Date(eventGroup[j].time_start).getTime() + userTimezoneOffset) : null;
                eventGroup[j].time_end = eventGroup[j].time_end ? new Date(new Date(eventGroup[j].time_end).getTime() + userTimezoneOffset) : null;
            }
        }
        return obj;
    }

    /*
     * Similar to loadDigestDates, but clones the object
     *
     * obj: digest object to convert the dates of
     */
    $scope.saveDigestDates = function(obj) {
        // Clone the object to avoid making changes to the scope variable.
        // This means that field values in the view won't suddenly change
        // after saving.
        let temp = JSON.parse(JSON.stringify(obj));
        var userTimezoneOffset = new Date().getTimezoneOffset() * 60000;
        temp.date = temp.date ? new Date(new Date(temp.date) - userTimezoneOffset) : null;
        var digestEventGroupKeys = Object.keys(temp.events);
        for (var i = 0; i < digestEventGroupKeys.length; i++) {
            var eventGroup = temp.events[digestEventGroupKeys[i]];
            for (var j = 0; j < eventGroup.length; j++) {
                eventGroup[j].date_start = eventGroup[j].date_start ? new Date(new Date(eventGroup[j].date_start) - userTimezoneOffset) : null;
                eventGroup[j].date_end = eventGroup[j].date_end ? new Date(new Date(eventGroup[j].date_end) - userTimezoneOffset) : null;
                eventGroup[j].time_start = eventGroup[j].time_start ? new Date(new Date(eventGroup[j].time_start) - userTimezoneOffset) : null;
                eventGroup[j].time_end = eventGroup[j].time_end ? new Date(new Date(eventGroup[j].time_end) - userTimezoneOffset) : null;
            }
        }
        return temp;
    }

    /*
     * Load digest information if it exists in LocalStorage (i.e. the user
     * clicked "Save to my computer" before and has not cleared cookies).
     */
    $scope.loadDigestLocally = function() {
        var temp_text = localStorage.getItem("over_ez_temp");

        if (temp_text && temp_text != "ignore") {
            try {
                let temp_digest = JSON.parse(temp_text);
                $scope.digest = $scope.loadDigestDates(temp_digest);
            }
            catch (err) {
                $scope.showFailure(err);
                console.error("couldn't load saved digest from LocalStorage");
            }
        }
        else if (!temp_text) {
            $scope.getDefault();
        }
    }

    // Load whatever's in local storage on page load
    $scope.loadDigestLocally();

    /*
     * Load digest details from the server.
     *
     * This function sends an event, DigestLoaded, to notify jsdom
     * of completion for serverside rendering tasks. By listening
     * for this event, the plugin will know when the HTML is done
     * generating in order to attach it to an email.
     *
     * dateStr: formatted date string (yyyy-MM-dd)
     */
    $scope.loadDigestRemotely = function(dateStr) {
        $http.get("/get/" + dateStr)
        .then(
            function(response){ // success
                $scope.digest = $scope.loadDigestDates(response.data);
                var event = new Event('DigestLoaded');

                // Dispatch the event.
                document.dispatchEvent(event);
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.error("couldn't load saved digest from server - " + dateStr);
            }
        );
    }

    /*
     * Saves all digest details to an entry in browser LocalStorage
     */
    $scope.saveDigestLocally = function () {
        try {
            $scope.showSuccess();
            localStorage.setItem("over_ez_temp", JSON.stringify($scope.saveDigestDates($scope.digest)));
        }
        catch(err) {
            $scope.showFailure(err);
            localStorage.removeItem("over_ez_temp");
        }
    }

    /*
     * Saves all digest details to the server (the way the POST request
     * is defined, the server will overwrite a database entry if it has
     * a matching date)
     *
     * callback: optional function to run on success (usually publishDigest)
     */
    $scope.saveDigestRemotely = function (callback) {
        var digest = $scope.saveDigestDates($scope.digest);
        $http.post("/save", digest)
            .then(
                function(response){ // success
                    localStorage.removeItem("over_ez_temp");
                    $scope.getList();
                    if (callback) {
                        setTimeout(function() {
                            callback(digest.date.toISOString().substr(0,10));
                        }, 250);
                    }
                    else {
                        console.log("successfully saved digest to server");
                        $scope.showSuccess();
                    }

                },
                function(response){ // failure
                    console.error("couldn't save digest to server");
                    $scope.showFailure(response.data);
                }
            );
    }

    /*
     * Publishes digest via email.
     *
     * dateStr: formatted date string (yyyy-MM-dd)
     */
    $scope.publishDigest = function (dateStr) {
        $scope.publishing = true;
        $http.post("/render/" + dateStr)
            .then(
                function(response){ // success
                    $("#publishModal").modal("hide");
                    setTimeout(function() {
                        $scope.publishing = false;
                    }, 250);
                    console.log("successfully published digest");
                    $scope.showSuccess();

                },
                function(response){ // failure
                    $("#publishModal").modal("hide");
                    setTimeout(function() {
                        $scope.publishing = false;
                    }, 250);
                    console.error("couldn't publish digest");
                    $scope.showFailure(response.data);
                }
            );
    }

    /*
     * Adds a blank item to the model.
     *
     * type: an integer value specifying the type of event
     *       that the new entry should be listed as
     */
    $scope.addItem = function (type) {
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                $scope.digest.events.special.push({});
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                $scope.digest.events.service.push({});
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                $scope.digest.events.fellowship.push({});
                break;
            case $scope.ENTITY_COMMITTEE:
                $scope.digest.committees.push({});
                break;
            default:
                break;
        }
    }

    /*
     * Shows or hides a prompt to delete some item from the model.
     *
     * type:          an integer value specifying the type of the targeted item
     * index:         the index of the target item within the array that corresponds
     *                to the the given type
     * displayPrompt: a boolean variable; show the prompt if true, hide if false
     */
    $scope.removeItemPrompt = function (type, index, displayPrompt) {
        var entityArr = null;
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }
        entityArr[index]._toRemove = displayPrompt;
    }

    /*
     * Deletes an item from the model (if the user confirms the delete prompt)
     *
     * type:          an integer value specifying the type of the targeted item
     * index:         the index of the target item within the array that corresponds
     *                to the the given type
     */
    $scope.removeItem = function (type, index) {
        var entityArr = null;

        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }
        entityArr.splice(index, 1);
    }

    /*
     * Sets an event's start or end date to some date in the next week.
     *
     * type:       an integer value specifying the type of the targeted item
     * index:      the index of the target item within the array that corresponds
     *             to the the given type
     * setEndDate: a boolean variable; show the end date if true, set the start date if false
     * value:      an integer denoting which day of the week to set the event to
     */
    $scope.setEventDate = function (type, index, setEndDate, value) {
        var entityArr = null;
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }

        let today = new Date(Date.now());

        let newDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());

        // advance 1 day first
        newDate.setDate(newDate.getDate() + 1);

        while (newDate.getDay() != value) {
          newDate.setDate(newDate.getDate() + 1);
        }
        if (setEndDate) {
            entityArr[index].date_end = newDate;
        } else {
            entityArr[index].date_start = newDate;
        }
    }

    $scope.showSuccess = function(message) {
        $("#successModal").modal("show");
    }

    $scope.showFailure = function(err) {
        $("#failureModal").modal("show");
        console.error(err);
    }

    $scope.showPublishModal = function() {
        $http.get("/publishEmail")
        .then(
            function(response){ // success
                $scope.emailTo = response.data.emailTo;
                $("#publishModal").modal("show");
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.log("couldn't get publish email address");
            }
        );
    }
})
.directive("outputEvent", function() {
    return {
        templateUrl: function(elem, attr) {
            return '/templates/output-event.html';
        }
    };
})
.directive("editorEvent", function() {
    return {
        restrict: 'E',
        scope: true,
        link: function(scope, element, attrs) {
            scope.type = parseInt(attrs.type);
            scope.eventIndex = parseInt(attrs.index);
        },
        templateUrl: function(elem, attr) {
            return '/templates/editor-event.html';
        }
    };
});
<commit_msg>Handle DST offset correctly across the time change boundary<commit_after>var app = angular.module("over_ez", []);
app.controller("DigestController", function($scope, $http) {
    $scope.ENTITY_EVENT_SPECIAL = 3420;
    $scope.ENTITY_EVENT_SERVICE = 3421;
    $scope.ENTITY_EVENT_FELLOWSHIP = 3422;
    $scope.ENTITY_COMMITTEE = 3423;

    $scope.daysOfWeek = [
        { value: 0, label: "...the next Sunday" },
        { value: 1, label: "...the next Monday" },
        { value: 2, label: "...the next Tuesday" },
        { value: 3, label: "...the next Wednesday" },
        { value: 4, label: "...the next Thursday" },
        { value: 5, label: "...the next Friday" },
        { value: 6, label: "...the next Saturday" }
    ]

    $scope.page = {
        showAllInOpen: false
    }

    /*
     * Get a list of digests saved to the server.
     */
    $scope.getList = function() {
        $http.get("/list")
        .then(
            function(response){ // success
                $scope.server_digest_list = response.data;
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.log("couldn't get saved digests from server");
            }
        );
    }

    $scope.getList();

    /*
     * Get the default digest configuration from the server.
     */
    $scope.getDefault = function() {
        $http.get("/get")
        .then(
            function(response){ // success
                $scope.digest = $scope.loadDigestDates(response.data);
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.error("couldn't get default digest information from server, using local hardcoded defaults")
                $scope.digest = {
                    "date" : null,
                    "cssFile" : "digest.css",
                    "message" : {
                        "enable" : false,
                        "body" : "",
                        "signoff1" : "In LFS,",
                        "signoff2" : ""
                    },
                    "events" : {
                        "special" : [],
                        "service" : [],
                        "fellowship" : []
                    },
                    "maintainer" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "svp" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "fvp" : {
                        "name" : "",
                        "nickname" : "",
                        "email" : ""
                    },
                    "committees" : [],
                    "meetingMinutes" : [],
                    "meetingsUrl" : ""
                }
            }
        );
    }

    /*
     * HTML date and time fields attempt to convert the entered date or
     * time into the local time zone. As the dates and times that are
     * entered will always be in Eastern Time (for RPI's campus), it's
     * not necessary to support multiple time zones. By adding the
     * current local time offset to UTC when loading digest information
     * and subtracting it when saving digest information, we ensure that
     * users see dates and times in Eastern Time wherever they are.
     *
     * obj: digest object to convert the dates of
     */
    $scope.loadDigestDates = function(obj) {
        var userTimezoneOffset = new Date(obj ? obj.date : null).getTimezoneOffset() * 60000;
        obj.date = obj.date ? new Date(new Date(obj.date).getTime() + userTimezoneOffset) : null;
        var digestEventGroupKeys = Object.keys(obj.events);
        for (var i = 0; i < digestEventGroupKeys.length; i++) {
            var eventGroup = obj.events[digestEventGroupKeys[i]];
            for (var j = 0; j < eventGroup.length; j++) {
                userTimezoneOffset = new Date(eventGroup[j].date_start).getTimezoneOffset() * 60000;
                eventGroup[j].date_start = eventGroup[j].date_start ? new Date(new Date(eventGroup[j].date_start) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].date_end).getTimezoneOffset() * 60000;
                eventGroup[j].date_end = eventGroup[j].date_end ? new Date(new Date(eventGroup[j].date_end) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].time_start).getTimezoneOffset() * 60000;
                eventGroup[j].time_start = eventGroup[j].time_start ? new Date(new Date(eventGroup[j].time_start) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].time_end).getTimezoneOffset() * 60000;
                eventGroup[j].time_end = eventGroup[j].time_end ? new Date(new Date(eventGroup[j].time_end) - userTimezoneOffset) : null;
            }
        }
        return obj;
    }

    /*
     * Similar to loadDigestDates, but clones the object
     *
     * obj: digest object to convert the dates of
     */
    $scope.saveDigestDates = function(obj) {
        // Clone the object to avoid making changes to the scope variable.
        // This means that field values in the view won't suddenly change
        // after saving.
        let temp = JSON.parse(JSON.stringify(obj));
        var userTimezoneOffset = new Date(temp ? temp.date : null).getTimezoneOffset() * 60000;
        temp.date = temp.date ? new Date(new Date(temp.date) - userTimezoneOffset) : null;
        var digestEventGroupKeys = Object.keys(temp.events);
        for (var i = 0; i < digestEventGroupKeys.length; i++) {
            var eventGroup = temp.events[digestEventGroupKeys[i]];
            for (var j = 0; j < eventGroup.length; j++) {
                userTimezoneOffset = new Date(eventGroup[j].date_start).getTimezoneOffset() * 60000;
                eventGroup[j].date_start = eventGroup[j].date_start ? new Date(new Date(eventGroup[j].date_start) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].date_end).getTimezoneOffset() * 60000;
                eventGroup[j].date_end = eventGroup[j].date_end ? new Date(new Date(eventGroup[j].date_end) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].time_start).getTimezoneOffset() * 60000;
                eventGroup[j].time_start = eventGroup[j].time_start ? new Date(new Date(eventGroup[j].time_start) - userTimezoneOffset) : null;
                userTimezoneOffset = new Date(eventGroup[j].time_end).getTimezoneOffset() * 60000;
                eventGroup[j].time_end = eventGroup[j].time_end ? new Date(new Date(eventGroup[j].time_end) - userTimezoneOffset) : null;
            }
        }
        return temp;
    }

    /*
     * Load digest information if it exists in LocalStorage (i.e. the user
     * clicked "Save to my computer" before and has not cleared cookies).
     */
    $scope.loadDigestLocally = function() {
        var temp_text = localStorage.getItem("over_ez_temp");

        if (temp_text && temp_text != "ignore") {
            try {
                let temp_digest = JSON.parse(temp_text);
                $scope.digest = $scope.loadDigestDates(temp_digest);
            }
            catch (err) {
                $scope.showFailure(err);
                console.error("couldn't load saved digest from LocalStorage");
            }
        }
        else if (!temp_text) {
            $scope.getDefault();
        }
    }

    // Load whatever's in local storage on page load
    $scope.loadDigestLocally();

    /*
     * Load digest details from the server.
     *
     * This function sends an event, DigestLoaded, to notify jsdom
     * of completion for serverside rendering tasks. By listening
     * for this event, the plugin will know when the HTML is done
     * generating in order to attach it to an email.
     *
     * dateStr: formatted date string (yyyy-MM-dd)
     */
    $scope.loadDigestRemotely = function(dateStr) {
        $http.get("/get/" + dateStr)
        .then(
            function(response){ // success
                $scope.digest = $scope.loadDigestDates(response.data);
                var event = new Event('DigestLoaded');

                // Dispatch the event.
                document.dispatchEvent(event);
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.error("couldn't load saved digest from server - " + dateStr);
            }
        );
    }

    /*
     * Saves all digest details to an entry in browser LocalStorage
     */
    $scope.saveDigestLocally = function () {
        try {
            $scope.showSuccess();
            localStorage.setItem("over_ez_temp", JSON.stringify($scope.saveDigestDates($scope.digest)));
        }
        catch(err) {
            $scope.showFailure(err);
            localStorage.removeItem("over_ez_temp");
        }
    }

    /*
     * Saves all digest details to the server (the way the POST request
     * is defined, the server will overwrite a database entry if it has
     * a matching date)
     *
     * callback: optional function to run on success (usually publishDigest)
     */
    $scope.saveDigestRemotely = function (callback) {
        var digest = $scope.saveDigestDates($scope.digest);
        $http.post("/save", digest)
            .then(
                function(response){ // success
                    localStorage.removeItem("over_ez_temp");
                    $scope.getList();
                    if (callback) {
                        setTimeout(function() {
                            callback(digest.date.toISOString().substr(0,10));
                        }, 250);
                    }
                    else {
                        console.log("successfully saved digest to server");
                        $scope.showSuccess();
                    }

                },
                function(response){ // failure
                    console.error("couldn't save digest to server");
                    $scope.showFailure(response.data);
                }
            );
    }

    /*
     * Publishes digest via email.
     *
     * dateStr: formatted date string (yyyy-MM-dd)
     */
    $scope.publishDigest = function (dateStr) {
        $scope.publishing = true;
        $http.post("/render/" + dateStr)
            .then(
                function(response){ // success
                    $("#publishModal").modal("hide");
                    setTimeout(function() {
                        $scope.publishing = false;
                    }, 250);
                    console.log("successfully published digest");
                    $scope.showSuccess();

                },
                function(response){ // failure
                    $("#publishModal").modal("hide");
                    setTimeout(function() {
                        $scope.publishing = false;
                    }, 250);
                    console.error("couldn't publish digest");
                    $scope.showFailure(response.data);
                }
            );
    }

    /*
     * Adds a blank item to the model.
     *
     * type: an integer value specifying the type of event
     *       that the new entry should be listed as
     */
    $scope.addItem = function (type) {
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                $scope.digest.events.special.push({});
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                $scope.digest.events.service.push({});
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                $scope.digest.events.fellowship.push({});
                break;
            case $scope.ENTITY_COMMITTEE:
                $scope.digest.committees.push({});
                break;
            default:
                break;
        }
    }

    /*
     * Shows or hides a prompt to delete some item from the model.
     *
     * type:          an integer value specifying the type of the targeted item
     * index:         the index of the target item within the array that corresponds
     *                to the the given type
     * displayPrompt: a boolean variable; show the prompt if true, hide if false
     */
    $scope.removeItemPrompt = function (type, index, displayPrompt) {
        var entityArr = null;
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }
        entityArr[index]._toRemove = displayPrompt;
    }

    /*
     * Deletes an item from the model (if the user confirms the delete prompt)
     *
     * type:          an integer value specifying the type of the targeted item
     * index:         the index of the target item within the array that corresponds
     *                to the the given type
     */
    $scope.removeItem = function (type, index) {
        var entityArr = null;

        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }
        entityArr.splice(index, 1);
    }

    /*
     * Sets an event's start or end date to some date in the next week.
     *
     * type:       an integer value specifying the type of the targeted item
     * index:      the index of the target item within the array that corresponds
     *             to the the given type
     * setEndDate: a boolean variable; show the end date if true, set the start date if false
     * value:      an integer denoting which day of the week to set the event to
     */
    $scope.setEventDate = function (type, index, setEndDate, value) {
        var entityArr = null;
        switch (type) {
            case $scope.ENTITY_EVENT_SPECIAL:
                entityArr = $scope.digest.events.special;
                break;
            case $scope.ENTITY_EVENT_SERVICE:
                entityArr = $scope.digest.events.service;
                break;
            case $scope.ENTITY_EVENT_FELLOWSHIP:
                entityArr = $scope.digest.events.fellowship;
                break;
            case $scope.ENTITY_COMMITTEE:
                entityArr = $scope.digest.committees;
                break;
            default:
                break;
        }

        let today = new Date(Date.now());

        let newDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());

        // advance 1 day first
        newDate.setDate(newDate.getDate() + 1);

        while (newDate.getDay() != value) {
          newDate.setDate(newDate.getDate() + 1);
        }
        if (setEndDate) {
            entityArr[index].date_end = newDate;
        } else {
            entityArr[index].date_start = newDate;
        }
    }

    $scope.showSuccess = function(message) {
        $("#successModal").modal("show");
    }

    $scope.showFailure = function(err) {
        $("#failureModal").modal("show");
        console.error(err);
    }

    $scope.showPublishModal = function() {
        $http.get("/publishEmail")
        .then(
            function(response){ // success
                $scope.emailTo = response.data.emailTo;
                $("#publishModal").modal("show");
            },
            function(response){ // failure
                $scope.showFailure(response.data);
                console.log("couldn't get publish email address");
            }
        );
    }
})
.directive("outputEvent", function() {
    return {
        templateUrl: function(elem, attr) {
            return '/templates/output-event.html';
        }
    };
})
.directive("editorEvent", function() {
    return {
        restrict: 'E',
        scope: true,
        link: function(scope, element, attrs) {
            scope.type = parseInt(attrs.type);
            scope.eventIndex = parseInt(attrs.index);
        },
        templateUrl: function(elem, attr) {
            return '/templates/editor-event.html';
        }
    };
});
<|endoftext|>


<commit_before>'use strict'

var fs = require('fs')
var path = require('path')
var afterAll = require('after-all-results')
var errorCallsites = require('error-callsites')
var debug = require('debug')('stackman')

var isAbsolute = path.isAbsolute || require('path-is-absolute')

var cache = require('async-cache')({
  max: 500,
  load: function (file, cb) {
    debug('reading %s', file)
    fs.readFile(file, {encoding: 'utf8'}, cb)
  }
})

var LINES_OF_CONTEXT = 7
var ESCAPED_REGEX_PATH_SEP = path.sep === '/' ? '/' : '\\\\'
var MODULE_FOLDER_REGEX = new RegExp('.*node_modules' + ESCAPED_REGEX_PATH_SEP + '([^' + ESCAPED_REGEX_PATH_SEP + ']*)')

exports.callsites = callsites
exports.properties = properties
exports.sourceContexts = sourceContexts

function callsites (err, opts, cb) {
  if (typeof opts === 'function') return callsites(err, null, opts)
  if (!opts) opts = {}
  if (!('sourcemap' in opts)) opts.sourcemap = true

  var _callsites = errorCallsites(err)

  if (!validStack(_callsites)) {
    process.nextTick(function () {
      cb(new Error('Could not process callsites'))
    })
    return
  }

  _callsites.forEach(function (callsite) {
    Object.defineProperties(callsite, {
      getRelativeFileName: {
        writable: false,
        value: getRelativeFileName
      },
      getTypeNameSafely: {
        writable: false,
        value: getTypeNameSafely
      },
      getFunctionNameSanitized: {
        writable: false,
        value: getFunctionNameSanitized
      },
      getModuleName: {
        writable: false,
        value: getModuleName
      },
      isApp: {
        writable: false,
        value: isApp
      },
      isModule: {
        writable: false,
        value: isModule
      },
      isNode: {
        writable: false,
        value: isNode
      },
      sourceContext: {
        writable: false,
        value: sourceContext
      }
    })
  })

  // TODO: Load sourcemap if `opts.sourcemap === true`
  process.nextTick(function () {
    cb(null, _callsites)
  })
}

function properties (err) {
  var properties = {}
  Object.keys(err).forEach(function (key) {
    if (key === 'stack') return // 'stack' seems to be enumerable in Node 0.11
    var val = err[key]
    if (val === null) return // null is typeof object and well break the switch below
    switch (typeof val) {
      case 'function':
        return
      case 'object':
        // ignore all objects except Dates
        if (typeof val.toISOString !== 'function') return
        val = val.toISOString()
    }
    properties[key] = val
  })
  return properties
}

function sourceContexts (callsites, opts, cb) {
  if (typeof opts === 'function') return sourceContexts(callsites, null, opts)
  if (!opts) opts = {}

  var next = afterAll(cb)

  callsites.forEach(function (callsite) {
    if (callsite.isNode()) {
      next()(null, null)
    } else {
      callsite.sourceContext(opts, next())
    }
  })
}

function validStack (callsites) {
  return Array.isArray(callsites) &&
         typeof callsites[0] === 'object' &&
         typeof callsites[0].getFileName === 'function'
}

function getRelativeFileName () {
  var filename = this.getFileName()
  if (!filename) return
  var root = process.cwd()
  if (root[root.length - 1] !== path.sep) root += path.sep
  return !~filename.indexOf(root) ? filename : filename.substr(root.length)
}

function getTypeNameSafely () {
  try {
    return this.getTypeName()
  } catch (e) {
    // This seems to happen sometimes when using 'use strict',
    // stemming from `getTypeName`.
    // [TypeError: Cannot read property 'constructor' of undefined]
    return null
  }
}

function getFunctionNameSanitized () {
  var fnName = this.getFunctionName()
  if (fnName) return fnName
  var typeName = this.getTypeNameSafely()
  if (typeName) return typeName + '.' + (this.getMethodName() || '<anonymous>')
  return '<anonymous>'
}

function getModuleName () {
  var filename = this.getFileName() || ''
  var match = filename.match(MODULE_FOLDER_REGEX)
  return match ? match[1] : null
}

function isApp () {
  return !this.isNode() && !~(this.getFileName() || '').indexOf('node_modules' + path.sep)
}

function isModule () {
  return !!~(this.getFileName() || '').indexOf('node_modules' + path.sep)
}

function isNode () {
  if (this.isNative()) return true
  var filename = this.getFileName() || ''
  return (!isAbsolute(filename) && filename[0] !== '.')
}

function sourceContext (opts, cb) {
  if (this.isNode()) {
    return process.nextTick(cb, new Error('Can\'t get source context of a Node core callsite'))
  }
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  var callsite = this
  var filename = this.getFileName() || ''

  cache.get(filename, function (err, source) {
    if (err) {
      debug('error reading %s: %s', filename, err.message)
      cb(err)
    } else {
      cb(null, parseSource(source, callsite, opts))
    }
  })
}

function parseSource (source, callsite, opts) {
  var lines = source.split(/\r?\n/)
  var linesOfContext = opts.lines || LINES_OF_CONTEXT
  var lineno = callsite.getLineNumber()
  return {
    pre: lines.slice(Math.max(0, lineno - (linesOfContext + 1)), lineno - 1),
    line: lines[lineno - 1],
    post: lines.slice(lineno, lineno + linesOfContext)
  }
}
<commit_msg>Code cleanup<commit_after>'use strict'

var fs = require('fs')
var path = require('path')
var afterAll = require('after-all-results')
var errorCallsites = require('error-callsites')
var debug = require('debug')('stackman')

var isAbsolute = path.isAbsolute || require('path-is-absolute')

var fileCache = require('async-cache')({
  max: 500,
  load: function (file, cb) {
    debug('reading %s', file)
    fs.readFile(file, {encoding: 'utf8'}, cb)
  }
})

var LINES_OF_CONTEXT = 7
var ESCAPED_REGEX_PATH_SEP = path.sep === '/' ? '/' : '\\\\'
var MODULE_FOLDER_REGEX = new RegExp('.*node_modules' + ESCAPED_REGEX_PATH_SEP + '([^' + ESCAPED_REGEX_PATH_SEP + ']*)')

exports.callsites = callsites
exports.properties = properties
exports.sourceContexts = sourceContexts

function callsites (err, opts, cb) {
  if (typeof opts === 'function') return callsites(err, null, opts)
  if (!opts) opts = {}
  if (!('sourcemap' in opts)) opts.sourcemap = true

  var _callsites = errorCallsites(err)

  if (!validStack(_callsites)) {
    process.nextTick(function () {
      cb(new Error('Could not process callsites'))
    })
    return
  }

  _callsites.forEach(function (callsite) {
    Object.defineProperties(callsite, {
      getRelativeFileName: {
        writable: false,
        value: getRelativeFileName
      },
      getTypeNameSafely: {
        writable: false,
        value: getTypeNameSafely
      },
      getFunctionNameSanitized: {
        writable: false,
        value: getFunctionNameSanitized
      },
      getModuleName: {
        writable: false,
        value: getModuleName
      },
      isApp: {
        writable: false,
        value: isApp
      },
      isModule: {
        writable: false,
        value: isModule
      },
      isNode: {
        writable: false,
        value: isNode
      },
      sourceContext: {
        writable: false,
        value: sourceContext
      }
    })
  })

  // TODO: Load sourcemap if `opts.sourcemap === true`
  process.nextTick(function () {
    cb(null, _callsites)
  })
}

function properties (err) {
  var properties = {}
  Object.keys(err).forEach(function (key) {
    if (key === 'stack') return // 'stack' seems to be enumerable in Node 0.11
    var val = err[key]
    if (val === null) return // null is typeof object and well break the switch below
    switch (typeof val) {
      case 'function':
        return
      case 'object':
        // ignore all objects except Dates
        if (typeof val.toISOString !== 'function') return
        val = val.toISOString()
    }
    properties[key] = val
  })
  return properties
}

function sourceContexts (callsites, opts, cb) {
  if (typeof opts === 'function') return sourceContexts(callsites, null, opts)
  if (!opts) opts = {}

  var next = afterAll(cb)

  callsites.forEach(function (callsite) {
    if (callsite.isNode()) {
      next()(null, null)
    } else {
      callsite.sourceContext(opts, next())
    }
  })
}

function validStack (callsites) {
  return Array.isArray(callsites) &&
         typeof callsites[0] === 'object' &&
         typeof callsites[0].getFileName === 'function'
}

function getRelativeFileName () {
  var filename = this.getFileName()
  if (!filename) return
  var root = process.cwd()
  if (root[root.length - 1] !== path.sep) root += path.sep
  return !~filename.indexOf(root) ? filename : filename.substr(root.length)
}

function getTypeNameSafely () {
  try {
    return this.getTypeName()
  } catch (e) {
    // This seems to happen sometimes when using 'use strict',
    // stemming from `getTypeName`.
    // [TypeError: Cannot read property 'constructor' of undefined]
    return null
  }
}

function getFunctionNameSanitized () {
  var fnName = this.getFunctionName()
  if (fnName) return fnName
  var typeName = this.getTypeNameSafely()
  if (typeName) return typeName + '.' + (this.getMethodName() || '<anonymous>')
  return '<anonymous>'
}

function getModuleName () {
  var filename = this.getFileName() || ''
  var match = filename.match(MODULE_FOLDER_REGEX)
  return match ? match[1] : null
}

function isApp () {
  return !this.isNode() && !~(this.getFileName() || '').indexOf('node_modules' + path.sep)
}

function isModule () {
  return !!~(this.getFileName() || '').indexOf('node_modules' + path.sep)
}

function isNode () {
  if (this.isNative()) return true
  var filename = this.getFileName() || ''
  return (!isAbsolute(filename) && filename[0] !== '.')
}

function sourceContext (opts, cb) {
  if (this.isNode()) {
    return process.nextTick(cb, new Error('Can\'t get source context of a Node core callsite'))
  }
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  var callsite = this
  var filename = this.getFileName() || ''

  fileCache.get(filename, function (err, source) {
    if (err) {
      debug('error reading %s: %s', filename, err.message)
      cb(err)
    } else {
      cb(null, parseSource(source, callsite, opts))
    }
  })
}

function parseSource (source, callsite, opts) {
  var lines = source.split(/\r?\n/)
  var linesOfContext = opts.lines || LINES_OF_CONTEXT
  var lineno = callsite.getLineNumber()
  return {
    pre: lines.slice(Math.max(0, lineno - (linesOfContext + 1)), lineno - 1),
    line: lines[lineno - 1],
    post: lines.slice(lineno, lineno + linesOfContext)
  }
}
<|endoftext|>


<commit_before>const {expect} = require('chai');

const Scanner = require('../src/scanner.js');

describe('Scanner', () => {
    const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut convallis turpis velit,\nid ullamcorper nulla\ntristique vel. Morbi vel massa in lacus euismod facilisis.\nEtiam quis est velit. Suspendisse\nmolestie ultricies lectus, in vehicula neque\nfaucibus eget. Phasellus\nvenenatis tincidunt massa. Mauris\nlaoreet luctus\nfinibus. Donec sit amet quam\nquis."

  it('works with no text', () => {
    const scanner = new Scanner();

    const position = scanner.position;
    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(position);

    scanner.ungetChar();
    expect(scanner.position).to.equal(position);
  });

  it('works with an empty string', () => {
    const scanner = new Scanner('');

    const position = scanner.position;
    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(position);

    scanner.ungetChar();
    expect(scanner.position).to.equal(position);
  });

  it('should get characters accurately', () => {
    const scanner = new Scanner(SMALL_TEXT);

    for (let i = 0; i < SMALL_TEXT.length; i++) {
      const c = scanner.getChar();
      expect(c).to.equal(SMALL_TEXT[i]);
      expect(scanner.position).to.equal(i);
    }

    expect(scanner.getChar()).to.equal(Scanner.EOF);
  });

  it('should unget characters correctly', () => {
    const scanner = new Scanner(SMALL_TEXT);

    for (let i = 0; i < SMALL_TEXT.length; i++) {
      const c = scanner.getChar();
    }

    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(SMALL_TEXT.length-1);
    for (let i = SMALL_TEXT.length-1; i >= 0; i--) {
      expect(scanner.position).to.equal(i);

      scanner.ungetChar();
      expect(scanner.getChar()).to.equal(SMALL_TEXT[i]);

      scanner.ungetChar();
    }
  });
});
<commit_msg>Removed js from require<commit_after>const {expect} = require('chai');

const Scanner = require('../src/scanner');

describe('Scanner', () => {
    const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut convallis turpis velit,\nid ullamcorper nulla\ntristique vel. Morbi vel massa in lacus euismod facilisis.\nEtiam quis est velit. Suspendisse\nmolestie ultricies lectus, in vehicula neque\nfaucibus eget. Phasellus\nvenenatis tincidunt massa. Mauris\nlaoreet luctus\nfinibus. Donec sit amet quam\nquis."

  it('works with no text', () => {
    const scanner = new Scanner();

    const position = scanner.position;
    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(position);

    scanner.ungetChar();
    expect(scanner.position).to.equal(position);
  });

  it('works with an empty string', () => {
    const scanner = new Scanner('');

    const position = scanner.position;
    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(position);

    scanner.ungetChar();
    expect(scanner.position).to.equal(position);
  });

  it('should get characters accurately', () => {
    const scanner = new Scanner(SMALL_TEXT);

    for (let i = 0; i < SMALL_TEXT.length; i++) {
      const c = scanner.getChar();
      expect(c).to.equal(SMALL_TEXT[i]);
      expect(scanner.position).to.equal(i);
    }

    expect(scanner.getChar()).to.equal(Scanner.EOF);
  });

  it('should unget characters correctly', () => {
    const scanner = new Scanner(SMALL_TEXT);

    for (let i = 0; i < SMALL_TEXT.length; i++) {
      const c = scanner.getChar();
    }

    expect(scanner.getChar()).to.equal(Scanner.EOF);
    expect(scanner.position).to.equal(SMALL_TEXT.length-1);
    for (let i = SMALL_TEXT.length-1; i >= 0; i--) {
      expect(scanner.position).to.equal(i);

      scanner.ungetChar();
      expect(scanner.getChar()).to.equal(SMALL_TEXT[i]);

      scanner.ungetChar();
    }
  });
});
<|endoftext|>


<commit_before>var mongoose = require( "mongoose" );

var Schema = mongoose.Schema;

var TransitSystemSchema = Schema( {
    name: String,
    system: String
} );

TransitSystemSchema.index( {
    system: 1
} );

TransitSystemSchema.statics.searchByName = function ( name, done ) {

    this.find( { name: new RegExp( name, "i" ) } )
        .select( "name urls" )
        .sort( { name: 1 } )
        .exec( done );

};

TransitSystemSchema.statics.all = function ( done ) {

    this.find( {} )
        .select( "name urls" )
        .sort( { name: 1 } )
        .exec( done );

};

exports.TransitSystem = mongoose.model( "transit_systems", TransitSystemSchema );
<commit_msg>Don't need this<commit_after>var mongoose = require( "mongoose" );

var Schema = mongoose.Schema;

var TransitSystemSchema = Schema( {
    name: String,
    system: String
} );

TransitSystemSchema.index( {
    system: 1
} );

TransitSystemSchema.statics.searchByName = function ( name, done ) {

    this.find( { name: new RegExp( name, "i" ) } )
        .sort( { name: 1 } )
        .exec( done );

};

TransitSystemSchema.statics.all = function ( done ) {

    this.find( {} )
        .sort( { name: 1 } )
        .exec( done );

};

exports.TransitSystem = mongoose.model( "transit_systems", TransitSystemSchema );
<|endoftext|>


<commit_before>var RegularExpression = Backbone.Model.extend({
  defaults: {
    expression: ''
  },
  validate: function(attrs){

  }
});

var RegularExpressions = Backbone.Collection.extend({
  model: RegularExpression
})

var RegExInputView = Backbone.View.extend({
  el: '#regex-input-section',
  initialize: function(){
    this.regexInputEle = this.$el.find("#regex-input");
  },
  events: {
    'click input[type=button]': 'addRegEx'
  },
  addRegEx: function() {
    var value = this.regexInputEle.val();
    if( value.length > 0) {
      this.collection.add({
        expression: value
      });
     console.log(this.collection);
    }
    console.log("i was called");
  }
});

$(function() {
  var regularExpressions = new RegularExpressions();
  var regexInputView = new RegExInputView({
    collection: regularExpressions
  });
  console.log("we are ready");
});
<commit_msg>cleanup<commit_after>var RegularExpression = Backbone.Model.extend({
  defaults: {
    expression: ''
  },
  validate: function(attrs){

  }
});

var RegularExpressions = Backbone.Collection.extend({
  model: RegularExpression
})

var RegExInputView = Backbone.View.extend({
  el: '#regex-input-section',
  initialize: function(){
    this.regexInputEle = this.$el.find("#regex-input");
  },
  events: {
    'click input[type=button]': 'addRegEx'
  },
  addRegEx: function() {
    var value = this.regexInputEle.val();
    if( value.length > 0) {
      this.collection.add({
        expression: value
      });
    }
  }
});

$(function() {
  var regularExpressions = new RegularExpressions();
  var regexInputView = new RegExInputView({
    collection: regularExpressions
  });
  console.log("we are ready");
});
<|endoftext|>