<commit_before>import { selectAll } from './dom';
import * as bug from './debug';

function scrollama() {
  const OBSERVER_NAMES = [
    'stepAbove',
    'stepBelow',
    'stepProgress',
    'viewportAbove',
    'viewportBelow'
  ];

  const cb = {
    stepEnter: () => {},
    stepExit: () => {},
    stepProgress: () => {}
  };
  const io = {};

  let id = null;
  let stepEl = [];
  let stepOffsetHeight = [];
  let stepOffsetTop = [];
  let stepStates = [];

  let offsetVal = 0;
  let offsetMargin = 0;
  let viewH = 0;
  let pageH = 0;
  let previousScrolledPx = 0;
  let progressThreshold = 0;

  let isReady = false;
  let isEnabled = false;
  let isDebug = false;

  let progressMode = false;
  let preserveOrder = false;
  let triggerOnce = false;

  let direction = 'down';

  let containerEl = null;

  const exclude = [];

  /*** HELPERS ***/
  function generateInstanceID() {
    const a = 'abcdefghijklmnopqrstuv';
    const l = a.length;
    const t = Date.now();
    const r = [0, 0, 0].map(d => a[Math.floor(Math.random() * l)]).join('');
    return `${r}${t}`;
  }

  function getOffsetTop(el) {
    const { top } = el.getBoundingClientRect();
    const scrollTop = window.pageYOffset;
    const clientTop = document.body.clientTop || 0;
    return top + scrollTop - clientTop;
  }

  function getPageHeight() {
    const body = document.body;
    const html = document.documentElement;
    if (containerEl) return Math.max(
        containerEl.scrollHeight,
        containerEl.offsetHeight,
        body.scrollHeight,
        body.offsetHeight,
        html.clientHeight,
        html.scrollHeight,
        html.offsetHeight
      );
    return Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
  }

  function getIndex(element) {
    return +element.getAttribute('data-scrollama-index');
  }

  function updateDirection() {
    const scrolledPx = containerEl ? containerEl.scrollTop : window.pageYOffset;

    if (scrolledPx > previousScrolledPx) direction = 'down';
    else if (scrolledPx < previousScrolledPx) direction = 'up';
    previousScrolledPx = scrolledPx;
  }

  function disconnectObserver(name) {
    if (io[name]) io[name].forEach(d => d.disconnect());
  }

  function handleResize() {
    viewH = window.innerHeight;
    pageH = getPageHeight();

    offsetMargin = offsetVal * viewH;

    if (isReady) {
      stepOffsetHeight = stepEl.map(el => el.getBoundingClientRect().height);
      stepOffsetTop = stepEl.map(getOffsetTop);
      if (isEnabled) updateIO();
    }

    if (isDebug) bug.update({ id, stepOffsetHeight, offsetMargin, offsetVal });
  }

  function handleEnable(enable) {
    if (enable && !isEnabled) { // enable a disabled scroller
      if (isReady) { // enable a ready scroller
        updateIO();
      } else { // can't enable an unready scroller
        console.error('scrollama error: enable() called before scroller was ready');
        isEnabled = false;
        return; // all is not well, don't set the requested state
      }
    }
    if (!enable && isEnabled) { // disable an enabled scroller
      OBSERVER_NAMES.forEach(disconnectObserver);
    }
    isEnabled = enable; // all is well, set requested state
  }

  function createThreshold(height) {
    const count = Math.ceil(height / progressThreshold);
    const t = [];
    const ratio = 1 / count;
    for (let i = 0; i < count; i++) {
      t.push(i * ratio);
    }
    return t;
  }

  /*** NOTIFY CALLBACKS ***/

  function notifyStepProgress(element, progress) {
    const index = getIndex(element);
    if (progress !== undefined) stepStates[index].progress = progress;
    const resp = { element, index, progress: stepStates[index].progress };

    if (stepStates[index].state === 'enter') cb.stepProgress(resp);
  }

  function notifyOthers(index, location) {
    if (location === 'above') {
      // check if steps above/below were skipped and should be notified first
      for (let i = 0; i < index; i++) {
        const ss = stepStates[i];
        if (ss.state !== 'enter' && ss.direction !== 'down') {
          notifyStepEnter(stepEl[i], 'down', false);
          notifyStepExit(stepEl[i], 'down');
        } else if (ss.state === 'enter') notifyStepExit(stepEl[i], 'down');
        // else if (ss.direction === 'up') {
        //   notifyStepEnter(stepEl[i], 'down', false);
        //   notifyStepExit(stepEl[i], 'down');
        // }
      }
    } else if (location === 'below') {
      for (let i = stepStates.length - 1; i > index; i--) {
        const ss = stepStates[i];
        if (ss.state === 'enter') {
          notifyStepExit(stepEl[i], 'up');
        }
        if (ss.direction === 'down') {
          notifyStepEnter(stepEl[i], 'up', false);
          notifyStepExit(stepEl[i], 'up');
        }
      }
    }
  }

  function notifyStepEnter(element, direction, check = true) {
    const index = getIndex(element);
    const resp = { element, index, direction };

    // store most recent trigger
    stepStates[index].direction = direction;
    stepStates[index].state = 'enter';
    if (preserveOrder && check && direction === 'down')
      notifyOthers(index, 'above');

    if (preserveOrder && check && direction === 'up')
      notifyOthers(index, 'below');

    if (cb.stepEnter && !exclude[index]) {
      cb.stepEnter(resp, stepStates);
      if (isDebug) bug.notifyStep({ id, index, state: 'enter' });
      if (triggerOnce) exclude[index] = true;
    }

    if (progressMode) notifyStepProgress(element);
  }

  function notifyStepExit(element, direction) {
    const index = getIndex(element);
    const resp = { element, index, direction };

    if (progressMode) {
      if (direction === 'down' && stepStates[index].progress < 1)
        notifyStepProgress(element, 1);
      else if (direction === 'up' && stepStates[index].progress > 0)
        notifyStepProgress(element, 0);
    }

    // store most recent trigger
    stepStates[index].direction = direction;
    stepStates[index].state = 'exit';

    cb.stepExit(resp, stepStates);
    if (isDebug) bug.notifyStep({ id, index, state: 'exit' });
  }

  /*** OBSERVER - INTERSECT HANDLING ***/
  // this is good for entering while scrolling down + leaving while scrolling up
  function intersectStepAbove([entry]) {
    updateDirection();
    const { isIntersecting, boundingClientRect, target } = entry;

    // bottom = bottom edge of element from top of viewport
    // bottomAdjusted = bottom edge of element from trigger
    const { top, bottom } = boundingClientRect;
    const topAdjusted = top - offsetMargin;
    const bottomAdjusted = bottom - offsetMargin;
    const index = getIndex(target);
    const ss = stepStates[index];

    // entering above is only when topAdjusted is negative
    // and bottomAdjusted is positive
    if (
      isIntersecting &&
      topAdjusted <= 0 &&
      bottomAdjusted >= 0 &&
      direction === 'down' &&
      ss.state !== 'enter'
    )
      notifyStepEnter(target, direction);

    // exiting from above is when topAdjusted is positive and not intersecting
    if (
      !isIntersecting &&
      topAdjusted > 0 &&
      direction === 'up' &&
      ss.state === 'enter'
    )
      notifyStepExit(target, direction);
  }

  // this is good for entering while scrolling up + leaving while scrolling down
  function intersectStepBelow([entry]) {
    updateDirection();
    const { isIntersecting, boundingClientRect, target } = entry;

    // bottom = bottom edge of element from top of viewport
    // bottomAdjusted = bottom edge of element from trigger
    const { top, bottom } = boundingClientRect;
    const topAdjusted = top - offsetMargin;
    const bottomAdjusted = bottom - offsetMargin;
    const index = getIndex(target);
    const ss = stepStates[index];

    // entering below is only when bottomAdjusted is positive
    // and topAdjusted is negative
    if (
      isIntersecting &&
      topAdjusted <= 0 &&
      bottomAdjusted >= 0 &&
      direction === 'up' &&
      ss.state !== 'enter'
    )
      notifyStepEnter(target, direction);

    // exiting from above is when bottomAdjusted is negative and not intersecting
    if (
      !isIntersecting &&
      bottomAdjusted < 0 &&
      direction === 'down' &&
      ss.state === 'enter'
    )
      notifyStepExit(target, direction);
  }

  /*
	if there is a scroll event where a step never intersects (therefore
	skipping an enter/exit trigger), use this fallback to detect if it is
	in view
	*/
  function intersectViewportAbove([entry]) {
    updateDirection();
    const { isIntersecting, target } = entry;
    const index = getIndex(target);
    const ss = stepStates[index];

    if (
      isIntersecting &&
      direction === 'down' &&
      ss.direction !== 'down' &&
      ss.state !== 'enter'
    ) {
      notifyStepEnter(target, 'down');
      notifyStepExit(target, 'down');
    }
  }

  function intersectViewportBelow([entry]) {
    updateDirection();
    const { isIntersecting, target } = entry;
    const index = getIndex(target);
    const ss = stepStates[index];
    if (
      isIntersecting &&
      direction === 'up' &&
      ss.direction === 'down' &&
      ss.state !== 'enter'
    ) {
      notifyStepEnter(target, 'up');
      notifyStepExit(target, 'up');
    }
  }

  function intersectStepProgress([entry]) {
    updateDirection();
    const {
      isIntersecting,
      intersectionRatio,
      boundingClientRect,
      target
    } = entry;
    const { bottom } = boundingClientRect;
    const bottomAdjusted = bottom - offsetMargin;
    if (isIntersecting && bottomAdjusted >= 0) {
      notifyStepProgress(target, +intersectionRatio.toFixed(3));
    }
  }

  /***  OBSERVER - CREATION ***/
  // jump into viewport
  function updateViewportAboveIO() {
    io.viewportAbove = stepEl.map((el, i) => {
      const marginTop = pageH - stepOffsetTop[i];
      const marginBottom = offsetMargin - viewH - stepOffsetHeight[i];
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectViewportAbove, options);
      obs.observe(el);
      return obs;
    });
  }

  function updateViewportBelowIO() {
    io.viewportBelow = stepEl.map((el, i) => {
      const marginTop = -offsetMargin - stepOffsetHeight[i];
      const marginBottom = offsetMargin - viewH + stepOffsetHeight[i] + pageH;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectViewportBelow, options);
      obs.observe(el);
      return obs;
    });
  }

  // look above for intersection
  function updateStepAboveIO() {
    io.stepAbove = stepEl.map((el, i) => {
      const marginTop = -offsetMargin + stepOffsetHeight[i];
      const marginBottom = offsetMargin - viewH;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepAbove, options);
      obs.observe(el);
      return obs;
    });
  }

  // look below for intersection
  function updateStepBelowIO() {
    io.stepAbove = stepEl.map((el, i) => {
      const marginTop = -offsetMargin;
      const marginBottom = offsetMargin - viewH + stepOffsetHeight[i];
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepBelow, options);
      obs.observe(el);
      return obs;
    });
  }

  // progress progress tracker
  function updateStepProgressIO() {
    io.stepProgress = stepEl.map((el, i) => {
      const marginTop = stepOffsetHeight[i] - offsetMargin;
      const marginBottom = -viewH + offsetMargin;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const threshold = createThreshold(stepOffsetHeight[i]);
      const options = { rootMargin, threshold };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepProgress, options);
      obs.observe(el);
      return obs;
    });
  }

  function updateIO() {
    OBSERVER_NAMES.forEach(disconnectObserver);

    updateViewportAboveIO();
    updateViewportBelowIO();
    updateStepAboveIO();
    updateStepBelowIO();

    if (progressMode) updateStepProgressIO();
  }

  /*** SETUP FUNCTIONS ***/

  function indexSteps() {
    stepEl.forEach((el, i) => el.setAttribute('data-scrollama-index', i));
  }

  function setupStates() {
    stepStates = stepEl.map(() => ({
      direction: null,
      state: null,
      progress: 0
    }));
  }

  function addDebug() {
    if (isDebug) bug.setup({ id, stepEl, offsetVal });
  }

  const S = {};

  S.setup = ({
    step,
    offset = 0.5,
    progress = false,
    threshold = 4,
    debug = false,
    order = true,
    once = false,
    container = false
  }) => {
    // create id unique to this scrollama instance
    id = generateInstanceID();

    stepEl = selectAll(step);

    if (!stepEl.length) {
      console.error('scrollama error: no step elements');
      return S;
    }

    // options
    isDebug = debug;
    progressMode = progress;
    preserveOrder = order;
    triggerOnce = once;
    containerEl = container;

    S.offsetTrigger(offset);
    progressThreshold = Math.max(1, +threshold);

    isReady = true;

    // customize
    addDebug();
    indexSteps();
    setupStates();
    handleResize();
    S.enable();
    return S;
  };

  S.resize = () => {
    handleResize();
    return S;
  };

  S.enable = () => {
    handleEnable(true);
    return S;
  };

  S.disable = () => {
    handleEnable(false);
    return S;
  };

  S.destroy = () => {
    handleEnable(false);
    Object.keys(cb).forEach(c => (cb[c] = null));
    Object.keys(io).forEach(i => (io[i] = null));
  };

  S.offsetTrigger = x => {
    if (x && !isNaN(x)) {
      if (x > 1) console.error('scrollama error: offset value is greater than 1. Fallbacks to 1.');
      if (x < 0) console.error('scrollama error: offset value is lower than 0. Fallbacks to 0.');
      offsetVal = Math.min(Math.max(0, x), 1);
      return S;
    } else if (isNaN(x)) {
      console.error('scrollama error: offset value is not a number. Fallbacks to 0.');
    }
    return offsetVal;
  };

  S.onStepEnter = f => {
    if (typeof f === 'function') cb.stepEnter = f;
    else console.error('scrollama error: onStepEnter requires a function');
    return S;
  };

  S.onStepExit = f => {
    if (typeof f === 'function') cb.stepExit = f;
    else console.error('scrollama error: onStepExit requires a function');
    return S;
  };

  S.onStepProgress = f => {
    if (typeof f === 'function') cb.stepProgress = f;
    else console.error('scrollama error: onStepProgress requires a function');
    return S;
  };

  return S;
}

export default scrollama;
<commit_msg>Fix incorrect naming of stepAbove in the updateStepBelowIO() function that prevents all events from being disabled.<commit_after>import { selectAll } from './dom';
import * as bug from './debug';

function scrollama() {
  const OBSERVER_NAMES = [
    'stepAbove',
    'stepBelow',
    'stepProgress',
    'viewportAbove',
    'viewportBelow'
  ];

  const cb = {
    stepEnter: () => {},
    stepExit: () => {},
    stepProgress: () => {}
  };
  const io = {};

  let id = null;
  let stepEl = [];
  let stepOffsetHeight = [];
  let stepOffsetTop = [];
  let stepStates = [];

  let offsetVal = 0;
  let offsetMargin = 0;
  let viewH = 0;
  let pageH = 0;
  let previousScrolledPx = 0;
  let progressThreshold = 0;

  let isReady = false;
  let isEnabled = false;
  let isDebug = false;

  let progressMode = false;
  let preserveOrder = false;
  let triggerOnce = false;

  let direction = 'down';

  let containerEl = null;

  const exclude = [];

  /*** HELPERS ***/
  function generateInstanceID() {
    const a = 'abcdefghijklmnopqrstuv';
    const l = a.length;
    const t = Date.now();
    const r = [0, 0, 0].map(d => a[Math.floor(Math.random() * l)]).join('');
    return `${r}${t}`;
  }

  function getOffsetTop(el) {
    const { top } = el.getBoundingClientRect();
    const scrollTop = window.pageYOffset;
    const clientTop = document.body.clientTop || 0;
    return top + scrollTop - clientTop;
  }

  function getPageHeight() {
    const body = document.body;
    const html = document.documentElement;
    if (containerEl) return Math.max(
        containerEl.scrollHeight,
        containerEl.offsetHeight,
        body.scrollHeight,
        body.offsetHeight,
        html.clientHeight,
        html.scrollHeight,
        html.offsetHeight
      );
    return Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
  }

  function getIndex(element) {
    return +element.getAttribute('data-scrollama-index');
  }

  function updateDirection() {
    const scrolledPx = containerEl ? containerEl.scrollTop : window.pageYOffset;

    if (scrolledPx > previousScrolledPx) direction = 'down';
    else if (scrolledPx < previousScrolledPx) direction = 'up';
    previousScrolledPx = scrolledPx;
  }

  function disconnectObserver(name) {
    if (io[name]) io[name].forEach(d => d.disconnect());
  }

  function handleResize() {
    viewH = window.innerHeight;
    pageH = getPageHeight();

    offsetMargin = offsetVal * viewH;

    if (isReady) {
      stepOffsetHeight = stepEl.map(el => el.getBoundingClientRect().height);
      stepOffsetTop = stepEl.map(getOffsetTop);
      if (isEnabled) updateIO();
    }

    if (isDebug) bug.update({ id, stepOffsetHeight, offsetMargin, offsetVal });
  }

  function handleEnable(enable) {
    if (enable && !isEnabled) { // enable a disabled scroller
      if (isReady) { // enable a ready scroller
        updateIO();
      } else { // can't enable an unready scroller
        console.error('scrollama error: enable() called before scroller was ready');
        isEnabled = false;
        return; // all is not well, don't set the requested state
      }
    }
    if (!enable && isEnabled) { // disable an enabled scroller
      OBSERVER_NAMES.forEach(disconnectObserver);
    }
    isEnabled = enable; // all is well, set requested state
  }

  function createThreshold(height) {
    const count = Math.ceil(height / progressThreshold);
    const t = [];
    const ratio = 1 / count;
    for (let i = 0; i < count; i++) {
      t.push(i * ratio);
    }
    return t;
  }

  /*** NOTIFY CALLBACKS ***/

  function notifyStepProgress(element, progress) {
    const index = getIndex(element);
    if (progress !== undefined) stepStates[index].progress = progress;
    const resp = { element, index, progress: stepStates[index].progress };

    if (stepStates[index].state === 'enter') cb.stepProgress(resp);
  }

  function notifyOthers(index, location) {
    if (location === 'above') {
      // check if steps above/below were skipped and should be notified first
      for (let i = 0; i < index; i++) {
        const ss = stepStates[i];
        if (ss.state !== 'enter' && ss.direction !== 'down') {
          notifyStepEnter(stepEl[i], 'down', false);
          notifyStepExit(stepEl[i], 'down');
        } else if (ss.state === 'enter') notifyStepExit(stepEl[i], 'down');
        // else if (ss.direction === 'up') {
        //   notifyStepEnter(stepEl[i], 'down', false);
        //   notifyStepExit(stepEl[i], 'down');
        // }
      }
    } else if (location === 'below') {
      for (let i = stepStates.length - 1; i > index; i--) {
        const ss = stepStates[i];
        if (ss.state === 'enter') {
          notifyStepExit(stepEl[i], 'up');
        }
        if (ss.direction === 'down') {
          notifyStepEnter(stepEl[i], 'up', false);
          notifyStepExit(stepEl[i], 'up');
        }
      }
    }
  }

  function notifyStepEnter(element, direction, check = true) {
    const index = getIndex(element);
    const resp = { element, index, direction };

    // store most recent trigger
    stepStates[index].direction = direction;
    stepStates[index].state = 'enter';
    if (preserveOrder && check && direction === 'down')
      notifyOthers(index, 'above');

    if (preserveOrder && check && direction === 'up')
      notifyOthers(index, 'below');

    if (cb.stepEnter && !exclude[index]) {
      cb.stepEnter(resp, stepStates);
      if (isDebug) bug.notifyStep({ id, index, state: 'enter' });
      if (triggerOnce) exclude[index] = true;
    }

    if (progressMode) notifyStepProgress(element);
  }

  function notifyStepExit(element, direction) {
    const index = getIndex(element);
    const resp = { element, index, direction };

    if (progressMode) {
      if (direction === 'down' && stepStates[index].progress < 1)
        notifyStepProgress(element, 1);
      else if (direction === 'up' && stepStates[index].progress > 0)
        notifyStepProgress(element, 0);
    }

    // store most recent trigger
    stepStates[index].direction = direction;
    stepStates[index].state = 'exit';

    cb.stepExit(resp, stepStates);
    if (isDebug) bug.notifyStep({ id, index, state: 'exit' });
  }

  /*** OBSERVER - INTERSECT HANDLING ***/
  // this is good for entering while scrolling down + leaving while scrolling up
  function intersectStepAbove([entry]) {
    updateDirection();
    const { isIntersecting, boundingClientRect, target } = entry;

    // bottom = bottom edge of element from top of viewport
    // bottomAdjusted = bottom edge of element from trigger
    const { top, bottom } = boundingClientRect;
    const topAdjusted = top - offsetMargin;
    const bottomAdjusted = bottom - offsetMargin;
    const index = getIndex(target);
    const ss = stepStates[index];

    // entering above is only when topAdjusted is negative
    // and bottomAdjusted is positive
    if (
      isIntersecting &&
      topAdjusted <= 0 &&
      bottomAdjusted >= 0 &&
      direction === 'down' &&
      ss.state !== 'enter'
    )
      notifyStepEnter(target, direction);

    // exiting from above is when topAdjusted is positive and not intersecting
    if (
      !isIntersecting &&
      topAdjusted > 0 &&
      direction === 'up' &&
      ss.state === 'enter'
    )
      notifyStepExit(target, direction);
  }

  // this is good for entering while scrolling up + leaving while scrolling down
  function intersectStepBelow([entry]) {
    updateDirection();
    const { isIntersecting, boundingClientRect, target } = entry;

    // bottom = bottom edge of element from top of viewport
    // bottomAdjusted = bottom edge of element from trigger
    const { top, bottom } = boundingClientRect;
    const topAdjusted = top - offsetMargin;
    const bottomAdjusted = bottom - offsetMargin;
    const index = getIndex(target);
    const ss = stepStates[index];

    // entering below is only when bottomAdjusted is positive
    // and topAdjusted is negative
    if (
      isIntersecting &&
      topAdjusted <= 0 &&
      bottomAdjusted >= 0 &&
      direction === 'up' &&
      ss.state !== 'enter'
    )
      notifyStepEnter(target, direction);

    // exiting from above is when bottomAdjusted is negative and not intersecting
    if (
      !isIntersecting &&
      bottomAdjusted < 0 &&
      direction === 'down' &&
      ss.state === 'enter'
    )
      notifyStepExit(target, direction);
  }

  /*
	if there is a scroll event where a step never intersects (therefore
	skipping an enter/exit trigger), use this fallback to detect if it is
	in view
	*/
  function intersectViewportAbove([entry]) {
    updateDirection();
    const { isIntersecting, target } = entry;
    const index = getIndex(target);
    const ss = stepStates[index];

    if (
      isIntersecting &&
      direction === 'down' &&
      ss.direction !== 'down' &&
      ss.state !== 'enter'
    ) {
      notifyStepEnter(target, 'down');
      notifyStepExit(target, 'down');
    }
  }

  function intersectViewportBelow([entry]) {
    updateDirection();
    const { isIntersecting, target } = entry;
    const index = getIndex(target);
    const ss = stepStates[index];
    if (
      isIntersecting &&
      direction === 'up' &&
      ss.direction === 'down' &&
      ss.state !== 'enter'
    ) {
      notifyStepEnter(target, 'up');
      notifyStepExit(target, 'up');
    }
  }

  function intersectStepProgress([entry]) {
    updateDirection();
    const {
      isIntersecting,
      intersectionRatio,
      boundingClientRect,
      target
    } = entry;
    const { bottom } = boundingClientRect;
    const bottomAdjusted = bottom - offsetMargin;
    if (isIntersecting && bottomAdjusted >= 0) {
      notifyStepProgress(target, +intersectionRatio.toFixed(3));
    }
  }

  /***  OBSERVER - CREATION ***/
  // jump into viewport
  function updateViewportAboveIO() {
    io.viewportAbove = stepEl.map((el, i) => {
      const marginTop = pageH - stepOffsetTop[i];
      const marginBottom = offsetMargin - viewH - stepOffsetHeight[i];
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectViewportAbove, options);
      obs.observe(el);
      return obs;
    });
  }

  function updateViewportBelowIO() {
    io.viewportBelow = stepEl.map((el, i) => {
      const marginTop = -offsetMargin - stepOffsetHeight[i];
      const marginBottom = offsetMargin - viewH + stepOffsetHeight[i] + pageH;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectViewportBelow, options);
      obs.observe(el);
      return obs;
    });
  }

  // look above for intersection
  function updateStepAboveIO() {
    io.stepAbove = stepEl.map((el, i) => {
      const marginTop = -offsetMargin + stepOffsetHeight[i];
      const marginBottom = offsetMargin - viewH;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepAbove, options);
      obs.observe(el);
      return obs;
    });
  }

  // look below for intersection
  function updateStepBelowIO() {
    io.stepBelow = stepEl.map((el, i) => {
      const marginTop = -offsetMargin;
      const marginBottom = offsetMargin - viewH + stepOffsetHeight[i];
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const options = { rootMargin };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepBelow, options);
      obs.observe(el);
      return obs;
    });
  }

  // progress progress tracker
  function updateStepProgressIO() {
    io.stepProgress = stepEl.map((el, i) => {
      const marginTop = stepOffsetHeight[i] - offsetMargin;
      const marginBottom = -viewH + offsetMargin;
      const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
      const threshold = createThreshold(stepOffsetHeight[i]);
      const options = { rootMargin, threshold };
      // console.log(options);
      const obs = new IntersectionObserver(intersectStepProgress, options);
      obs.observe(el);
      return obs;
    });
  }

  function updateIO() {
    OBSERVER_NAMES.forEach(disconnectObserver);

    updateViewportAboveIO();
    updateViewportBelowIO();
    updateStepAboveIO();
    updateStepBelowIO();

    if (progressMode) updateStepProgressIO();
  }

  /*** SETUP FUNCTIONS ***/

  function indexSteps() {
    stepEl.forEach((el, i) => el.setAttribute('data-scrollama-index', i));
  }

  function setupStates() {
    stepStates = stepEl.map(() => ({
      direction: null,
      state: null,
      progress: 0
    }));
  }

  function addDebug() {
    if (isDebug) bug.setup({ id, stepEl, offsetVal });
  }

  const S = {};

  S.setup = ({
    step,
    offset = 0.5,
    progress = false,
    threshold = 4,
    debug = false,
    order = true,
    once = false,
    container = false
  }) => {
    // create id unique to this scrollama instance
    id = generateInstanceID();

    stepEl = selectAll(step);

    if (!stepEl.length) {
      console.error('scrollama error: no step elements');
      return S;
    }

    // options
    isDebug = debug;
    progressMode = progress;
    preserveOrder = order;
    triggerOnce = once;
    containerEl = container;

    S.offsetTrigger(offset);
    progressThreshold = Math.max(1, +threshold);

    isReady = true;

    // customize
    addDebug();
    indexSteps();
    setupStates();
    handleResize();
    S.enable();
    return S;
  };

  S.resize = () => {
    handleResize();
    return S;
  };

  S.enable = () => {
    handleEnable(true);
    return S;
  };

  S.disable = () => {
    handleEnable(false);
    return S;
  };

  S.destroy = () => {
    handleEnable(false);
    Object.keys(cb).forEach(c => (cb[c] = null));
    Object.keys(io).forEach(i => (io[i] = null));
  };

  S.offsetTrigger = x => {
    if (x && !isNaN(x)) {
      if (x > 1) console.error('scrollama error: offset value is greater than 1. Fallbacks to 1.');
      if (x < 0) console.error('scrollama error: offset value is lower than 0. Fallbacks to 0.');
      offsetVal = Math.min(Math.max(0, x), 1);
      return S;
    } else if (isNaN(x)) {
      console.error('scrollama error: offset value is not a number. Fallbacks to 0.');
    }
    return offsetVal;
  };

  S.onStepEnter = f => {
    if (typeof f === 'function') cb.stepEnter = f;
    else console.error('scrollama error: onStepEnter requires a function');
    return S;
  };

  S.onStepExit = f => {
    if (typeof f === 'function') cb.stepExit = f;
    else console.error('scrollama error: onStepExit requires a function');
    return S;
  };

  S.onStepProgress = f => {
    if (typeof f === 'function') cb.stepProgress = f;
    else console.error('scrollama error: onStepProgress requires a function');
    return S;
  };

  return S;
}

export default scrollama;
<|endoftext|>


<commit_before>import express from 'express';
import session from 'express-session';
import bodyParser from 'body-parser';
import config from '../src/config';
import * as actions from './actions/index';
import { mapUrl } from 'utils/url.js';
import PrettyError from 'pretty-error';
import http from 'http';

const pretty = new PrettyError();
const app = express();

const server = new http.Server(app);

app.use(session({
    secret: 'react and redux rule!!!!',
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 60000 },
}));
app.use(bodyParser.json());

app.use((req, res) => {
    const splittedUrlPath = req.url.split('?')[0].split('/').slice(1);

    const { action, params } = mapUrl(actions, splittedUrlPath);

    if (action) {
        action(req, params)
      .then((result) => {
          if (result instanceof Function) {
              result(res);
          } else {
              res.json(result);
          }
      }, (reason) => {
          if (reason && reason.redirect) {
              res.redirect(reason.redirect);
          } else {
              console.error('API ERROR:', pretty.render(reason));
              res.status(reason.status || 500).json(reason);
          }
      });
    } else {
        res.status(404).end('NOT FOUND');
    }
});

if (config.apiPort) {
    const runnable = app.listen(config.apiPort, (err) => {
        if (err) {
            console.error(err);
        }

        const green = '\x1b[34m';
        const red = '\x1b[31m';
        const dim = '\x1b[2m';
        console.log('\n', dim, '==================| API Ready |=================\n');
        console.log(green, `==> 🔮  API is running on port ${config.apiPort}`);
        console.log(green, '==> 💦  Send requests to', red, `http://${config.apiHost}:${config.apiPort}`);
    });
} else {
    console.error('==>     ERROR: No PORT environment variable has been specified');
}
<commit_msg>indent formatting<commit_after>import express from 'express';
import session from 'express-session';
import bodyParser from 'body-parser';
import config from '../src/config';
import * as actions from './actions/index';
import { mapUrl } from 'utils/url.js';
import PrettyError from 'pretty-error';
import http from 'http';

const pretty = new PrettyError();
const app = express();

const server = new http.Server(app);

app.use(session({
    secret: 'react and redux rule!!!!',
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 60000 },
}));
app.use(bodyParser.json());

app.use((req, res) => {
    const splittedUrlPath = req.url.split('?')[0].split('/').slice(1);

    const { action, params } = mapUrl(actions, splittedUrlPath);

    if (action) {
        action(req, params).then((result) => {
            if (result instanceof Function) {
                result(res);
            } else {
                res.json(result);
            }
        }, (reason) => {
            if (reason && reason.redirect) {
                res.redirect(reason.redirect);
            } else {
                console.error('API ERROR:', pretty.render(reason));
                res.status(reason.status || 500).json(reason);
            }
        });
    } else {
        res.status(404).end('NOT FOUND');
    }
});

if (config.apiPort) {
    const runnable = app.listen(config.apiPort, (err) => {
        if (err) {
            console.error(err);
        }

        const green = '\x1b[34m';
        const red = '\x1b[31m';
        const dim = '\x1b[2m';
        console.log('\n', dim, '==================| API Ready |=================\n');
        console.log(green, `==> 🔮  API is running on port ${config.apiPort}`);
        console.log(green, '==> 💦  Send requests to', red, `http://${config.apiHost}:${config.apiPort}`);
    });
} else {
    console.error('==>     ERROR: No PORT environment variable has been specified');
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose');
var errorHandler = require('./errors.server.controller');
var Vocablist = mongoose.model('Vocablist');
var _ = require('lodash');
var Vocab = mongoose.model('Vocab');

/**
 * Create a Vocablist
 */
exports.create = function(req, res) {
  var vocablist = new Vocablist(req.body);
  vocablist.user = req.user;

  vocablist.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * Show the current Vocablist
 */
exports.read = function(req, res) {
  res.jsonp(req.vocablist);
};

/**
 * Update a Vocablist
 */
exports.update = function(req, res) {
  var vocablist = req.vocablist ;

  vocablist = _.extend(vocablist , req.body);

  vocablist.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * Delete an Vocablist
 */
exports.delete = function(req, res) {
  var vocablist = req.vocablist;

  vocablist.remove(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * List of Vocablists
 */
exports.list = function(req, res) {
  Vocablist.find().sort('-created').populate('vocabs').exec(function(err, vocablists) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablists);
    }
  });
};

/**
 * Vocablist middleware
 */
exports.vocablistByID = function(req, res, next, id) {
  Vocablist.findById(id).exec(function(err, vocablist) {
    if (err) return next(err);
    if (!vocablist) return next(new Error('Failed to load Vocablist ' + id));
    req.vocablist = vocablist;
    next();
  });
};

/**
 * Vocablist authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
  if (req.vocablist.user.id !== req.user.id) {
    return res.status(403).send('User is not authorized');
  }
  next();
};
<commit_msg>Adds curly braces to if statement<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose');
var errorHandler = require('./errors.server.controller');
var Vocablist = mongoose.model('Vocablist');
var _ = require('lodash');
var Vocab = mongoose.model('Vocab');
var rsvp = require('rsvp');

/**
 * Create a Vocablist
 */
exports.create = function(req, res) {
  var vocablist = new Vocablist(req.body);
  vocablist.user = req.user;

  vocablist.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * Show the current Vocablist
 */
exports.read = function(req, res) {
  res.jsonp(req.vocablist);
};

/**
 * Update a Vocablist
 */
exports.update = function(req, res) {
  var vocablist = req.vocablist ;

  vocablist = _.extend(vocablist , req.body);

  vocablist.save(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * Delete an Vocablist
 */
exports.delete = function(req, res) {
  var vocablist = req.vocablist;

  vocablist.remove(function(err) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablist);
    }
  });
};

/**
 * List of Vocablists
 */
exports.list = function(req, res) {
  Vocablist.find().sort('-created').populate('vocabs').exec(function(err, vocablists) {
    if (err) {
      return res.status(400).send({
        message: errorHandler.getErrorMessage(err)
      });
    } else {
      res.jsonp(vocablists);
    }
  });
};

/**
 * Vocablist middleware
 */
exports.vocablistByID = function(req, res, next, id) {
  Vocablist.findById(id).exec(function(err, vocablist) {
    if (err) {
      return next(err);
    }
    if (!vocablist) {
      return next(new Error('Failed to load Vocablist ' + id));
    }
    req.vocablist = vocablist;
    next();
  });
};

/**
 * Vocablist authorization middleware
 */
exports.hasAuthorization = function(req, res, next) {
  if (req.vocablist.user.id !== req.user.id) {
    return res.status(403).send('User is not authorized');
  }
  next();
};
<|endoftext|>


<commit_before>var connectionBuilder = require('../jira/connection_builder');
var buildLine = require('../utils/build_line');

module.exports = function(){
  connectionBuilder(function(connection){
    connection.listIssueTypes(function(err, types) {
      if (err){
        console.log(err);
        return
      }

      console.log('');
      var label = types.length + ' total issue types';
      console.log(label);
      console.log(buildLine(label.length));

      console.log('');
      types = types.sort(function(a,b) {
        return (a.name < b.name)? -1 : 1;
      });
      types.forEach(function(t) {
        if (t.description && t.description.length) {
          console.log(t.name + ": " +  t.description);
        } else {
          console.log(t.name);
        }
      });
      console.log('');
    });
  });
}
<commit_msg>better formatting for issue types<commit_after>var connectionBuilder = require('../jira/connection_builder');
var buildLine = require('../utils/build_line');

module.exports = function(){
  connectionBuilder(function(connection){
    connection.listIssueTypes(function(err, types) {
      if (err){
        console.log(err);
        return
      }

      console.log('');
      var label = types.length + ' total issue types';
      console.log(label);
      console.log(buildLine(label.length));

      console.log('');
      types = types.sort(function(a,b) {
        return (a.name < b.name)? -1 : 1;
      });
      types.forEach(function(t) {
        if (t.description && t.description.length) {
          console.log("%s [%s]: %s", t.name, t.id, t.description);
          //console.log(t.id + " - " + t.name + ": " +  t.description);
        } else {
          console.log("%s [%s]", t.name, t.id);
        }
      });
      console.log('');
    });
  });
}
<|endoftext|>


<commit_before>/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

// @ts-check

/**
 * @typedef {import('playwright').Page} Page
 */

const
	delay = require('delay'),
	h = include('tests/helpers');

/**
 * Starts a test
 *
 * @param {Page} page
 * @param {!Object} params
 * @returns {!Promise<void>}
 */
module.exports = async (page, params) => {
	await h.utils.setup(page, params.context);

	const defaultItems = [
		{id: 'bar'},

		{
			id: 'foo',
			children: [
				{id: 'foo_1'},
				{id: 'foo_2'},

				{
					id: 'foo_3',
					children: [{id: 'foo_3_1'}]
				},

				{id: 'foo_4'},
				{id: 'foo_5'},
				{id: 'foo_6'}
			]
		}
	];

	beforeEach(async () => {
		await page.evaluate(() => {
			globalThis.removeCreatedComponents();
		});
	});

	describe('b-tree rendering data from the `items` prop', () => {
		it('initialization', async () => {
			const
				target = await init();

			await expectAsync(target.evaluate((ctx) => ctx.isFunctional === false))
				.toBeResolvedTo(true);

			const
				promises = await Promise.all(checkOptionTree({items: defaultItems, target})),
				checkboxes = await page.$$('.b-checkbox');

			expect(promises.length).toEqual(checkboxes.length);
		});

		it('all items unfolded by default', async () => {
			const items = [
				{id: 'bar'},

				{
					id: 'foo',
					children: [
						{id: 'foo_1'},
						{id: 'foo_2'},

						{
							id: 'foo_3',
							folded: false,
							children: [{id: 'foo_3_1', children: [{id: 'foo_3_1_1'}]}]
						}
					]
				}
			];

			const target = await init({items});
			await Promise.all(checkOptionTree({items, target}));
		});

		describe('setting of the external `renderFilter`', () => {
			it('renders by one with a timeout', async () => {
				await init({attrs: {
					renderChunks: 1,
					renderFilter: 'return () => new Promise((res) => setTimeout(() => res(true), 0.5.second()))'
				}});

				await h.bom.waitForIdleCallback(page);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(1);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(2);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(3);
			});

			it('renders using the context data', async () => {
				await init({
					attrs: {renderFilter: `return (ctx, item) => {
							if (ctx.level === 0 ) {
								return true;
							}

							return false;
					}`}
				});

				await h.bom.waitForIdleCallback(page);

				await expect((await page.$$('.b-checkbox')).length).toBe(2);
			});
		});

		it('setting of the external `nestedRenderFilter`', async () => {
			const items = [
				{
					id: 'foo',
					children: [
						{id: 'foo_1'},
						{id: 'foo_2'},

						{
							id: 'foo_3',
							children: [{id: 'foo_3_1', children: [{id: 'foo_3_1_1'}]}]
						}
					]
				},

				{id: 'bar'}
			];

			await init({
				items,
				attrs: {
					renderChunks: 1,
					nestedRenderFilter: 'return () => new Promise((res) => setTimeout(() => res(true), 0.3.second()))'
				}
			});

			await waitForCheckboxCount(2, 0);
			await waitForCheckboxCount(3);
			await waitForCheckboxCount(4);
			await waitForCheckboxCount(5);
			await waitForCheckboxCount(6);
			await waitForCheckboxCount(7);
		});

		async function init({items, attrs, content} = {}) {
			if (items == null) {
				items = defaultItems;
			}

			await page.evaluate(({items, attrs, content}) => {
				globalThis.removeCreatedComponents();

				parseParams(content);
				parseParams(attrs);

				const baseAttrs = {
					theme: 'demo',
					item: 'b-checkbox-functional',
					items,
					id: 'target',
					renderChunks: 2
				};

				const scheme = [
					{
						attrs: {
							...baseAttrs,
							...attrs
						},

						content
					}
				];

				globalThis.renderComponents('b-tree', scheme);

				function parseParams(obj) {
					Object.forEach(obj, (el, key) => {
						// eslint-disable-next-line no-new-func
						obj[key] = /return /.test(el) ? Function(el)() : el;
					});
				}
			}, {items, attrs, content});

			await h.bom.waitForIdleCallback(page);
			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');

			return h.component.waitForComponent(page, '#target');
		}
	});

	describe('b-tree rendering data from a data provider', () => {
		it('initialization', async () => {
			await init();
			await waitForCheckboxCount(14);
			await h.bom.waitForIdleCallback(page);

			expect((await page.$$('.b-checkbox')).length).toBe(14);
		});

		async function init() {
			await page.evaluate(() => {
				globalThis.removeCreatedComponents();

				const scheme = [
					{
						attrs: {
							theme: 'demo',
							dataProvider: 'demo.NestedList',
							item: 'b-checkbox-functional',
							id: 'target'
						}
					}
				];

				globalThis.renderComponents('b-tree', scheme);
			});

			await h.bom.waitForIdleCallback(page);

			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');
			return h.component.waitForComponent(page, '#target');
		}
	});

	describe('b-tree providing of the default slot', () => {
		it('initialization', async () => {
			const
				target = await init();

			await expectAsync(target.evaluate((ctx) => ctx.isFunctional === false))
				.toBeResolvedTo(true);

			const
				promises = await Promise.all(checkOptionTree({items: defaultItems, target})),
				refs = await h.dom.getRefs(page, 'item');

			expect(promises.length).toEqual(refs.length);
		});

		async function init() {
			await page.evaluate((items) => {
				const defaultSlot = {
					tag: 'div',
					content: 'Item',
					attrs: {
						'data-test-ref': 'item'
					}
				};

				const scheme = [
					{
						attrs: {
							items,
							id: 'target',
							theme: 'demo'
						},

						content: {
							default: defaultSlot
						}
					}
				];

				globalThis.renderComponents('b-tree', scheme);
			}, defaultItems);

			await h.bom.waitForIdleCallback(page);
			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');

			return h.component.waitForComponent(page, '#target');
		}
	});

	async function waitForCheckboxCount(v, timer = 300) {
		await delay(timer);
		await page.waitForFunction((v) => document.querySelectorAll('.b-checkbox').length === v, v);
		await expect((await page.$$('.b-checkbox')).length).toBe(v);
	};

	function getFoldedClass(target, value = true) {
		return target.evaluate(
			(ctx, v) => ctx.block.getFullElName('node', 'folded', v),
			value
		);
	}

	function checkOptionTree({items, target, queue = [], level = 0, foldSelector}) {
		items.forEach((item) => {
			const
				isBranch = Object.isArray(item.children);

			queue.push((async () => {
				const
					element = await h.dom.waitForEl(page, `[data-id$="-${item.id}"]`);

				const
					foldedPropValue = await target.evaluate((ctx) => ctx.folded),
					foldedInitModValue = item.folded != null ? item.folded : foldedPropValue,
					foldedClass = await getFoldedClass(target, foldedInitModValue);

				await expectAsync(
					element.getAttribute('class').then((className) => className.includes(foldedClass))
				).toBeResolvedTo(true);

				await expectAsync(element.getAttribute('data-level')).toBeResolvedTo(String(level));

				if (isBranch) {
					const
						selector = foldSelector || await target.evaluate((ctx) => `.${ctx.block.getFullElName('fold')}`),
						fold = await h.dom.waitForEl(element, selector);

					await expectAsync(
						element.getAttribute('class').then((className) => className.includes(foldedClass))
					).toBeResolvedTo(true);

					if (foldedInitModValue) {
						await fold.click();
					}
				}
			})());

			if (isBranch) {
				checkOptionTree({
					items: item.children,
					level: level + 1,
					target,
					queue,
					foldSelector
				});
			}
		});

		return queue;
	}
};
<commit_msg>Removed delays from test<commit_after>/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

// @ts-check

/**
 * @typedef {import('playwright').Page} Page
 */

const
	delay = require('delay'),
	h = include('tests/helpers');

/**
 * Starts a test
 *
 * @param {Page} page
 * @param {!Object} params
 * @returns {!Promise<void>}
 */
module.exports = async (page, params) => {
	await h.utils.setup(page, params.context);

	const defaultItems = [
		{id: 'bar'},

		{
			id: 'foo',
			children: [
				{id: 'foo_1'},
				{id: 'foo_2'},

				{
					id: 'foo_3',
					children: [{id: 'foo_3_1'}]
				},

				{id: 'foo_4'},
				{id: 'foo_5'},
				{id: 'foo_6'}
			]
		}
	];

	beforeEach(async () => {
		await page.evaluate(() => {
			globalThis.removeCreatedComponents();
		});
	});

	describe('b-tree rendering data from the `items` prop', () => {
		it('initialization', async () => {
			const
				target = await init();

			await expectAsync(target.evaluate((ctx) => ctx.isFunctional === false))
				.toBeResolvedTo(true);

			const
				promises = await Promise.all(checkOptionTree({items: defaultItems, target})),
				checkboxes = await page.$$('.b-checkbox');

			expect(promises.length).toEqual(checkboxes.length);
		});

		it('all items unfolded by default', async () => {
			const items = [
				{id: 'bar'},

				{
					id: 'foo',
					children: [
						{id: 'foo_1'},
						{id: 'foo_2'},

						{
							id: 'foo_3',
							folded: false,
							children: [{id: 'foo_3_1', children: [{id: 'foo_3_1_1'}]}]
						}
					]
				}
			];

			const target = await init({items});
			await Promise.all(checkOptionTree({items, target}));
		});

		describe('setting of the external `renderFilter`', () => {
			it('renders by one with a timeout', async () => {
				await init({attrs: {
					renderChunks: 1,
					renderFilter: 'return () => new Promise((res) => setTimeout(() => res(true), 0.5.second()))'
				}});

				await h.bom.waitForIdleCallback(page);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(1);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(2);

				await delay(500);
				await expect((await page.$$('.b-checkbox')).length).toBe(3);
			});

			it('renders using the context data', async () => {
				await init({
					attrs: {renderFilter: `return (ctx, item) => {
							if (ctx.level === 0 ) {
								return true;
							}

							return false;
					}`}
				});

				await h.bom.waitForIdleCallback(page);

				await expect((await page.$$('.b-checkbox')).length).toBe(2);
			});
		});

		it('setting of the external `nestedRenderFilter`', async () => {
			const items = [
				{
					id: 'foo',
					children: [
						{id: 'foo_1'},
						{id: 'foo_2'},

						{
							id: 'foo_3',
							children: [{id: 'foo_3_1', children: [{id: 'foo_3_1_1'}]}]
						}
					]
				},

				{id: 'bar'}
			];

			await init({
				items,
				attrs: {
					renderChunks: 1,
					nestedRenderFilter: 'return () => new Promise((res) => setTimeout(() => res(true), 0.3.second()))'
				}
			});

			await waitForCheckboxCount(2, 0);
			await waitForCheckboxCount(3);
			await waitForCheckboxCount(4);
			await waitForCheckboxCount(5);
			await waitForCheckboxCount(6);
			await waitForCheckboxCount(7);
		});

		async function init({items, attrs, content} = {}) {
			if (items == null) {
				items = defaultItems;
			}

			await page.evaluate(({items, attrs, content}) => {
				globalThis.removeCreatedComponents();

				parseParams(content);
				parseParams(attrs);

				const baseAttrs = {
					theme: 'demo',
					item: 'b-checkbox-functional',
					items,
					id: 'target',
					renderChunks: 2
				};

				const scheme = [
					{
						attrs: {
							...baseAttrs,
							...attrs
						},

						content
					}
				];

				globalThis.renderComponents('b-tree', scheme);

				function parseParams(obj) {
					Object.forEach(obj, (el, key) => {
						// eslint-disable-next-line no-new-func
						obj[key] = /return /.test(el) ? Function(el)() : el;
					});
				}
			}, {items, attrs, content});

			await h.bom.waitForIdleCallback(page);
			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');

			return h.component.waitForComponent(page, '#target');
		}
	});

	describe('b-tree rendering data from a data provider', () => {
		it('initialization', async () => {
			await init();
			await waitForCheckboxCount(14);
			await h.bom.waitForIdleCallback(page);

			expect((await page.$$('.b-checkbox')).length).toBe(14);
		});

		async function init() {
			await page.evaluate(() => {
				globalThis.removeCreatedComponents();

				const scheme = [
					{
						attrs: {
							theme: 'demo',
							dataProvider: 'demo.NestedList',
							item: 'b-checkbox-functional',
							id: 'target'
						}
					}
				];

				globalThis.renderComponents('b-tree', scheme);
			});

			await h.bom.waitForIdleCallback(page);

			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');
			return h.component.waitForComponent(page, '#target');
		}
	});

	describe('b-tree providing of the default slot', () => {
		it('initialization', async () => {
			const
				target = await init();

			await expectAsync(target.evaluate((ctx) => ctx.isFunctional === false))
				.toBeResolvedTo(true);

			const
				promises = await Promise.all(checkOptionTree({items: defaultItems, target})),
				refs = await h.dom.getRefs(page, 'item');

			expect(promises.length).toEqual(refs.length);
		});

		async function init() {
			await page.evaluate((items) => {
				const defaultSlot = {
					tag: 'div',
					content: 'Item',
					attrs: {
						'data-test-ref': 'item'
					}
				};

				const scheme = [
					{
						attrs: {
							items,
							id: 'target',
							theme: 'demo'
						},

						content: {
							default: defaultSlot
						}
					}
				];

				globalThis.renderComponents('b-tree', scheme);
			}, defaultItems);

			await h.bom.waitForIdleCallback(page);
			await h.component.waitForComponentStatus(page, '.b-tree', 'ready');

			return h.component.waitForComponent(page, '#target');
		}
	});

	async function waitForCheckboxCount(v) {
		await page.waitForFunction((v) => document.querySelectorAll('.b-checkbox').length === v, v);
		await expect((await page.$$('.b-checkbox')).length).toBe(v);
	};

	function getFoldedClass(target, value = true) {
		return target.evaluate(
			(ctx, v) => ctx.block.getFullElName('node', 'folded', v),
			value
		);
	}

	function checkOptionTree({items, target, queue = [], level = 0, foldSelector}) {
		items.forEach((item) => {
			const
				isBranch = Object.isArray(item.children);

			queue.push((async () => {
				const
					element = await h.dom.waitForEl(page, `[data-id$="-${item.id}"]`);

				const
					foldedPropValue = await target.evaluate((ctx) => ctx.folded),
					foldedInitModValue = item.folded != null ? item.folded : foldedPropValue,
					foldedClass = await getFoldedClass(target, foldedInitModValue);

				await expectAsync(
					element.getAttribute('class').then((className) => className.includes(foldedClass))
				).toBeResolvedTo(true);

				await expectAsync(element.getAttribute('data-level')).toBeResolvedTo(String(level));

				if (isBranch) {
					const
						selector = foldSelector || await target.evaluate((ctx) => `.${ctx.block.getFullElName('fold')}`),
						fold = await h.dom.waitForEl(element, selector);

					await expectAsync(
						element.getAttribute('class').then((className) => className.includes(foldedClass))
					).toBeResolvedTo(true);

					if (foldedInitModValue) {
						await fold.click();
					}
				}
			})());

			if (isBranch) {
				checkOptionTree({
					items: item.children,
					level: level + 1,
					target,
					queue,
					foldSelector
				});
			}
		});

		return queue;
	}
};
<|endoftext|>


<commit_before>const
	fs = require('fs-extra'),
	crypto = require('crypto'),
	logger = require('../logger'),
	Path = require('path'),
	extend = require('extend');

const DEF_CONF = {
	key : `${__dirname}/server.key`,
	cert : `${__dirname}/server.crt`,
	passphrase : null,
	ca : null,
	rejectUnauthorized : false
};

var DEF_PATH = __dirname;

/**
 * Overwrites {@link TLS.DEFAULT} properties
 * @memberof TLS
 * @param  {object} options TLS options ({@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options})
 * @param  {string} path Default files base path
 */
async function defaults(options, path) {
	extend(DEF_CONF,options);
	DEF_PATH = path;
}

/**
 * Merges a TLS options object with the {@link TLS.DEFAULT} one, and reads all
 * private key / certificate files.
 * @memberof TLS
 * @param  {object} options TLS/SSL options object
 * @param  {string} [path] Base path for files to be read
 * @return {Promise<object>} Configured options object
 */
async function configure(options, path) {
	path = path || DEF_PATH;

	let opts = extend(true,{},DEF_CONF,options);
	let cpath = Path.resolve(path,opts.cert);
	let kpath = Path.resolve(path,opts.key);

	logger.debug(`Loading tls key ${kpath}`);
	try {
		opts.key = await fs.readFile(kpath);
	}catch(err) {
		logger.warn('Using default tls key file',err);
		opts.key = await fs.readFile(`${__dirname}/server.key`);
	}

	logger.debug(`Loading tls cert ${cpath}`);
	try {
		opts.cert = await fs.readFile(cpath);
	}catch(err) {
		logger.warn('Using default tls cert file',err);
		opts.cert = await fs.readFile(`${__dirname}/server.crt`);
	}

	if(opts.ca) {
		try {
			let capath = Path.resolve(path,opts.ca);
			opts.ca = await fs.readFile(capath);
		}catch(err) {
			logger.warn('No tls CA file',err);
		}
	}

	return opts;
}

function encrypt(options, data) {
	let opts = extend(true,{},DEF_CONF,options);
	let cpath = options.cert? Path.resolve(opts.cert) : Path.resolve(DEF_PATH,opts.cert);
	logger.info(`Encryption using cert: ${cpath}`);

	let publicKey = fs.readFileSync(cpath, "utf8");
	let buffer = Buffer.from(data);
	let encrypted = crypto.publicEncrypt(publicKey, buffer);
	return encrypted.toString("base64");
}

function decrypt(options, data) {
	let opts = extend(true,{},DEF_CONF,options);
	let kpath = options.key? Path.resolve(opts.key) : Path.resolve(DEF_PATH,opts.key);
	logger.info(`Encryption using key: ${kpath}`);

	let privateKeyData = fs.readFileSync(kpath, "utf8");
	let privateKey = crypto.createPrivateKey({key:privateKeyData,passphrase:opts.passphrase||undefined});
	var buffer = Buffer.from(data, "base64");
	var decrypted = crypto.privateDecrypt(privateKey, buffer);
	return decrypted.toString("utf8");
}

/**
 * @namespace TLS
 * @description Utility module for TLS/SSL configuration of components. It provides
 * common default options and internal private key and certificate files, so you
 * don't have to configure TLS options for yourself (for test purposes; in real
 * environment you should provide production ready certificates). Since node
 * TLS options require you to previously load certificate files and provide the
 * bynary buffer data, TLS module allows you to pass simply the file paths.
 * @example
 * const TLS = require('nsyslog/lib/tls');
 * const https = require('https');
 *
 * let tlsopts = await TLS.configure({
 * 	cert : 'cert.pem',
 * 	key : 'key.pem',
 * 	ca : ['ca.pem','cert.pem'],
 * 	passphrase : 'mykeypasswd'
 * }, '/path/to/files');
 *
 * https.createServer(tlsopts, (req, res) => {
 * 	res.writeHead(200);
 * 	res.end('hello world\n');
 * }).listen(8000);
 */
const TLS = {
	defaults,
	configure,
	encrypt,
	decrypt,

	/**
	 * @type {Object}
	 * @see https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
	 * @description Default TLS options for SSL/TLS secure connections
	 */
	DEFAULT : DEF_CONF
};

module.exports = TLS;
<commit_msg>tls fix<commit_after>const
	fs = require('fs-extra'),
	crypto = require('crypto'),
	logger = require('../logger'),
	Path = require('path'),
	extend = require('extend');

const DEF_CONF = {
	key : `${__dirname}/server.key`,
	cert : `${__dirname}/server.crt`,
	passphrase : null,
	ca : null,
	rejectUnauthorized : false
};

var DEF_PATH = __dirname;

/**
 * Overwrites {@link TLS.DEFAULT} properties
 * @memberof TLS
 * @param  {object} options TLS options ({@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options})
 * @param  {string} path Default files base path
 */
async function defaults(options, path) {
	extend(DEF_CONF,options);
	DEF_PATH = path;
}

/**
 * Merges a TLS options object with the {@link TLS.DEFAULT} one, and reads all
 * private key / certificate files.
 * @memberof TLS
 * @param  {object} options TLS/SSL options object
 * @param  {string} [path] Base path for files to be read
 * @return {Promise<object>} Configured options object
 */
async function configure(options, path) {
	path = path || DEF_PATH;

	let opts = extend(true,{},DEF_CONF,options);
	let cpath = Path.resolve(path,opts.cert);
	let kpath = Path.resolve(path,opts.key);

	logger.debug(`Loading tls key ${kpath}`);
	try {
		opts.key = await fs.readFile(kpath);
	}catch(err) {
		logger.warn('Using default tls key file',err);
		opts.key = await fs.readFile(`${__dirname}/server.key`);
	}

	logger.debug(`Loading tls cert ${cpath}`);
	try {
		opts.cert = await fs.readFile(cpath);
	}catch(err) {
		logger.warn('Using default tls cert file',err);
		opts.cert = await fs.readFile(`${__dirname}/server.crt`);
	}

	if(opts.ca) {
		try {
			let capath = Path.resolve(path,opts.ca);
			opts.ca = await fs.readFile(capath);
		}catch(err) {
			logger.warn('No tls CA file',err);
		}
	}

	return opts;
}

function encrypt(options, data) {
	let opts = extend(true,{},DEF_CONF,options);
	let cpath = options.cert? Path.resolve(opts.cert) : Path.resolve(DEF_PATH,opts.cert);
	logger.info(`Encryption using cert: ${cpath}`);

	let publicKey = fs.readFileSync(cpath, "utf8");
	let buffer = Buffer.from(data);
	let encrypted = crypto.publicEncrypt(publicKey, buffer);
	return encrypted.toString("base64");
}

function decrypt(options, data) {
	let opts = extend(true,{},DEF_CONF,options);
	let kpath = options.key? Path.resolve(opts.key) : Path.resolve(DEF_PATH,opts.key);
	logger.info(`Encryption using key: ${kpath}`);

	let privateKeyData = fs.readFileSync(kpath, "utf8");
	let privateKey = {key:privateKeyData,passphrase:opts.passphrase||undefined};
	var buffer = Buffer.from(data, "base64");
	var decrypted = crypto.privateDecrypt(privateKey, buffer);
	return decrypted.toString("utf8");
}

/**
 * @namespace TLS
 * @description Utility module for TLS/SSL configuration of components. It provides
 * common default options and internal private key and certificate files, so you
 * don't have to configure TLS options for yourself (for test purposes; in real
 * environment you should provide production ready certificates). Since node
 * TLS options require you to previously load certificate files and provide the
 * bynary buffer data, TLS module allows you to pass simply the file paths.
 * @example
 * const TLS = require('nsyslog/lib/tls');
 * const https = require('https');
 *
 * let tlsopts = await TLS.configure({
 * 	cert : 'cert.pem',
 * 	key : 'key.pem',
 * 	ca : ['ca.pem','cert.pem'],
 * 	passphrase : 'mykeypasswd'
 * }, '/path/to/files');
 *
 * https.createServer(tlsopts, (req, res) => {
 * 	res.writeHead(200);
 * 	res.end('hello world\n');
 * }).listen(8000);
 */
const TLS = {
	defaults,
	configure,
	encrypt,
	decrypt,

	/**
	 * @type {Object}
	 * @see https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
	 * @description Default TLS options for SSL/TLS secure connections
	 */
	DEFAULT : DEF_CONF
};

module.exports = TLS;
<|endoftext|>


<commit_before>const db = require('../utilities/db.js')
const utils = require('../utilities/utils.js')

/**
 * Query and response helper
 */

const responseWithError = (res, err) => {
  let errMsg = 'No error log found'
  if (typeof err === 'string') errMsg = err
  if (err instanceof Error) errMsg = err.message
  res.status(500).json({
    success: false,
    err: errMsg
  })
}

const queryAndResponse = (params) => {
  const {
    sql,
    parse,
    req,
    res
  } = params

  // console.log(sql)

  if (!sql) throw new Error('sql is not defined')
  if (typeof sql !== 'string') {
    throw new Error('Type of sql is not string')
  }
  if (!res) throw new Error('res object is not defined')
  if (!req) throw new Error('req object is not defined')
  if (parse && typeof parse !== 'function') {
    throw new Error('Type of parse is not defined')
  }

  const connection = db.getConnection()
  new Promise(
    (resolve, reject) => {
      connection.query(sql,
        (err, results) => {
          if (err) {
            utils.log(err)
            reject(err)
          } else {
            resolve(results)
          }
        }
      )
    }
  ).then(
    (result) => res.status(200).json({
      success: true,
      data: parse ? parse(result) : result
    }),
    (err) => responseWithError(res, err)
  ).catch(
    (err) => responseWithError(res, err)
  )
}

/**
 * Transform to SQL
 */

const transformToSQL = {
  insert: (tableName, dataSet) => {
    let columnsList = []
    let valuesList = []
    for (let key in dataSet) {
      columnsList.push(key)
      valuesList.push(dataSet[key])
    }
    return `INSERT INTO ${tableName} (${columnsList.join(', ')}) ` +
      `VALUES (${valuesList.map((val) => `"${val}"`).join(', ')})`
  },
  update: (tableName, dataSet, where) => {
    let setCondition = []
    for (let key in dataSet) {
      setCondition.push(`${key} = "${dataSet[key]}"`)
    }
    return `UPDATE ${tableName} SET ${setCondition.join(', ')} WHERE ${where}`
  },
  /**
   * receive tableName and filterList which consists of the key and value which
   * is needed to be queried
   */
  filter: (tableName, filterList) => {
    const keys = Object.keys(filterList)
    let queryString = `SELECT * FROM ${tableName} WHERE `
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      queryString += `${key} = "${filterList[key]}"` + `${(i < keys.length - 1) ? ' AND ' : ''}`
    }
    return queryString
  },
  delete: (tableName, conditionList) => {
    const keys = Object.keys(conditionList)
    let queryString = `DELETE FROM ${tableName} WHERE `
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      queryString += `${key} = "${conditionList[key]}"` + `${(i < keys.length - 1) ? ' AND ' : ''}`
    }
    return queryString
  }
}

module.exports = {
  queryAndResponse,
  transformToSQL
}
<commit_msg>Force sql_mode to empty<commit_after>const db = require('../utilities/db.js')
const utils = require('../utilities/utils.js')

/**
 * Query and response helper
 */

const responseWithError = (res, err) => {
  let errMsg = 'No error log found'
  if (typeof err === 'string') errMsg = err
  if (err instanceof Error) errMsg = err.message
  res.status(500).json({
    success: false,
    err: errMsg
  })
}

const queryAndResponse = (params) => {
  const {
    sql,
    parse,
    req,
    res
  } = params

  // console.log(sql)

  if (!sql) throw new Error('sql is not defined')
  if (typeof sql !== 'string') {
    throw new Error('Type of sql is not string')
  }
  if (!res) throw new Error('res object is not defined')
  if (!req) throw new Error('req object is not defined')
  if (parse && typeof parse !== 'function') {
    throw new Error('Type of parse is not defined')
  }

  const connection = db.getConnection()
  new Promise((resolve, reject) => {
    connection.query('SET sql_mode = ""', (err, result) => {
      if (err) {
        utils.log(err)
        reject(err)
      } else {
        resolve('test')
      }
    })
  }).then(() => {
    return new Promise((resolve, reject) => {
      connection.query(sql, (err, results) => {
        if (err) {
          utils.log(err)
          reject(err)
        } else {
          console.log(results)
          resolve(results)
        }
      })
    })
  }).then((result) => {
    res.status(200).json({
      success: true,
      data: parse ? parse(result) : result
    })
  }).catch(
    (err) => responseWithError(res, err)
  )
}

/**
 * Transform to SQL
 */

const transformToSQL = {
  insert: (tableName, dataSet) => {
    let columnsList = []
    let valuesList = []
    for (let key in dataSet) {
      columnsList.push(key)
      valuesList.push(dataSet[key])
    }
    return `INSERT INTO ${tableName} (${columnsList.join(', ')}) ` +
      `VALUES (${valuesList.map((val) => `"${val}"`).join(', ')})`
  },
  update: (tableName, dataSet, where) => {
    let setCondition = []
    for (let key in dataSet) {
      setCondition.push(`${key} = "${dataSet[key]}"`)
    }
    return `UPDATE ${tableName} SET ${setCondition.join(', ')} WHERE ${where}`
  },
  /**
   * receive tableName and filterList which consists of the key and value which
   * is needed to be queried
   */
  filter: (tableName, filterList) => {
    const keys = Object.keys(filterList)
    let queryString = `SELECT * FROM ${tableName} WHERE `
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      queryString += `${key} = "${filterList[key]}"` + `${(i < keys.length - 1) ? ' AND ' : ''}`
    }
    return queryString
  },
  delete: (tableName, conditionList) => {
    const keys = Object.keys(conditionList)
    let queryString = `DELETE FROM ${tableName} WHERE `
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      queryString += `${key} = "${conditionList[key]}"` + `${(i < keys.length - 1) ? ' AND ' : ''}`
    }
    return queryString
  }
}

module.exports = {
  queryAndResponse,
  transformToSQL
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClientPeg} from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore";
import {objectKeyChanges} from "../../utils/objects";

/**
 * Gets and sets settings at the "room" level.
 */
export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler {
    constructor(watchManager) {
        super();

        this._watchers = watchManager;
        this._onEvent = this._onEvent.bind(this);
    }

    initMatrixClient(oldClient, newClient) {
        if (oldClient) {
            oldClient.removeListener("RoomState.events", this._onEvent);
        }

        newClient.on("RoomState.events", this._onEvent);
    }

    _onEvent(event, state, prevEvent) {
        const roomId = event.getRoomId();
        const room = this.client.getRoom(roomId);
        if (!room) throw new Error(`Unknown room caused state update: ${roomId}`);

        if (state !== room.currentState) return; // ignore state updates which are not current

        if (event.getType() === "org.matrix.room.preview_urls") {
            let val = event.getContent()['disable'];
            if (typeof (val) !== "boolean") {
                val = null;
            } else {
                val = !val;
            }

            this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
        } else if (event.getType() === "im.vector.web.settings") {
            // Figure out what changed and fire those updates
            const prevContent = prevEvent ? prevEvent.getContent() : {};
            const changedSettings = objectKeyChanges(prevContent, event.getContent());
            for (const settingName of changedSettings) {
                this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
            }
        }
    }

    getValue(settingName, roomId) {
        // Special case URL previews
        if (settingName === "urlPreviewsEnabled") {
            const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};

            // Check to make sure that we actually got a boolean
            if (typeof(content['disable']) !== "boolean") return null;
            return !content['disable'];
        }

        const settings = this._getSettings(roomId) || {};
        return settings[settingName];
    }

    setValue(settingName, roomId, newValue) {
        // Special case URL previews
        if (settingName === "urlPreviewsEnabled") {
            const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
            content['disable'] = !newValue;
            return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
        }

        const content = this._getSettings(roomId) || {};
        content[settingName] = newValue;
        return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
    }

    canSetValue(settingName, roomId) {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(roomId);

        let eventType = "im.vector.web.settings";
        if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls";

        if (!room) return false;
        return room.currentState.maySendStateEvent(eventType, cli.getUserId());
    }

    isSupported() {
        const cli = MatrixClientPeg.get();
        return cli !== undefined && cli !== null;
    }

    _getSettings(roomId, eventType = "im.vector.web.settings") {
        const room = MatrixClientPeg.get().getRoom(roomId);
        if (!room) return null;

        const event = room.currentState.getStateEvents(eventType, "");
        if (!event || !event.getContent()) return null;
        return event.getContent();
    }
}
<commit_msg>Soften warning about lack of rooms in setting updates<commit_after>/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClientPeg} from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore";
import {objectKeyChanges} from "../../utils/objects";

/**
 * Gets and sets settings at the "room" level.
 */
export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler {
    constructor(watchManager) {
        super();

        this._watchers = watchManager;
        this._onEvent = this._onEvent.bind(this);
    }

    initMatrixClient(oldClient, newClient) {
        if (oldClient) {
            oldClient.removeListener("RoomState.events", this._onEvent);
        }

        newClient.on("RoomState.events", this._onEvent);
    }

    _onEvent(event, state, prevEvent) {
        const roomId = event.getRoomId();
        const room = this.client.getRoom(roomId);

        // Note: the tests often fire setting updates that don't have rooms in the store, so
        // we fail softly here. We shouldn't assume that the state being fired is current
        // state, but we also don't need to explode just because we didn't find a room.
        if (!room) console.warn(`Unknown room caused setting update: ${roomId}`);
        if (room && state !== room.currentState) return; // ignore state updates which are not current

        if (event.getType() === "org.matrix.room.preview_urls") {
            let val = event.getContent()['disable'];
            if (typeof (val) !== "boolean") {
                val = null;
            } else {
                val = !val;
            }

            this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
        } else if (event.getType() === "im.vector.web.settings") {
            // Figure out what changed and fire those updates
            const prevContent = prevEvent ? prevEvent.getContent() : {};
            const changedSettings = objectKeyChanges(prevContent, event.getContent());
            for (const settingName of changedSettings) {
                this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
            }
        }
    }

    getValue(settingName, roomId) {
        // Special case URL previews
        if (settingName === "urlPreviewsEnabled") {
            const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};

            // Check to make sure that we actually got a boolean
            if (typeof(content['disable']) !== "boolean") return null;
            return !content['disable'];
        }

        const settings = this._getSettings(roomId) || {};
        return settings[settingName];
    }

    setValue(settingName, roomId, newValue) {
        // Special case URL previews
        if (settingName === "urlPreviewsEnabled") {
            const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
            content['disable'] = !newValue;
            return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
        }

        const content = this._getSettings(roomId) || {};
        content[settingName] = newValue;
        return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
    }

    canSetValue(settingName, roomId) {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(roomId);

        let eventType = "im.vector.web.settings";
        if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls";

        if (!room) return false;
        return room.currentState.maySendStateEvent(eventType, cli.getUserId());
    }

    isSupported() {
        const cli = MatrixClientPeg.get();
        return cli !== undefined && cli !== null;
    }

    _getSettings(roomId, eventType = "im.vector.web.settings") {
        const room = MatrixClientPeg.get().getRoom(roomId);
        if (!room) return null;

        const event = room.currentState.getStateEvents(eventType, "");
        if (!event || !event.getContent()) return null;
        return event.getContent();
    }
}
<|endoftext|>


<commit_before>'use strict';

import { Router } from 'express';
import * as validator from '../helpers/validator';
import * as bot from '../helpers/botkit';
import * as messages from '../helpers/messages';

const router = Router();

router.post('/interactive', (req, res, next) => {
    let payload = JSON.parse(req.body.payload);

    if (payload.callback_id === "general_help") {

        if (payload.actions[0].value === 'show_all_topics') {
            res.send(messages.help_options_message);
        }

        if (payload.actions[0].value === 'register_help') {
            res.send(messages.register_team_help_message);
        }

        if (payload.actions[0].value === 'share_help') {
            res.send(messages.share_channel_help_message);
        }

        if (payload.actions[0].value === 'available_help') {
            res.send(messages.available_channel_help_message);
        }
    }

    if (payload.callback_id.includes("join_shared_channel_")) {
        bot.join_shared_channel(payload, (reply) => {
            res.send(reply);
        });
    }
});

router.post('/commands/register', (req, res, next) => {
    let payload = req.body;

    if (validator.is_url(payload.text)) {
        bot.register_slack_team(payload.text, payload.team_id, (reply) => {
            res.send(reply);
        });
    } else {
        res.send(messages.invalid_register_command_reply);
    }
});

router.post('/commands/share', (req, res, next) => {
    let payload = req.body;

    bot.share_channel(payload.team_id, payload.channel_id, payload.channel_name, (reply) => {
        res.send(reply);
    });
});

router.post('/commands/available', (req, res, next) => {
    let payload = req.body;

    bot.get_available_channels(payload.channel_id, payload.text, (reply) => {
        res.send(reply);
    });
});

export default router;<commit_msg>Added endpoint and challenge logic<commit_after>'use strict';

import { Router } from 'express';
import * as validator from '../helpers/validator';
import * as bot from '../helpers/botkit';
import * as messages from '../helpers/messages';

const router = Router();

router.post('/interactive', (req, res, next) => {
    let payload = JSON.parse(req.body.payload);

    if (payload.callback_id === "general_help") {

        if (payload.actions[0].value === 'show_all_topics') {
            res.send(messages.help_options_message);
        }

        if (payload.actions[0].value === 'register_help') {
            res.send(messages.register_team_help_message);
        }

        if (payload.actions[0].value === 'share_help') {
            res.send(messages.share_channel_help_message);
        }

        if (payload.actions[0].value === 'available_help') {
            res.send(messages.available_channel_help_message);
        }
    }

    if (payload.callback_id.includes("join_shared_channel_")) {
        bot.join_shared_channel(payload, (reply) => {
            res.send(reply);
        });
    }
});

router.post('/events', (req, res, next) => {
    if (req.body.type === 'url_verification') {
        res.send(req.body.challenge);
    }
});

router.post('/commands/register', (req, res, next) => {
    let payload = req.body;

    if (validator.is_url(payload.text)) {
        bot.register_slack_team(payload.text, payload.team_id, (reply) => {
            res.send(reply);
        });
    } else {
        res.send(messages.invalid_register_command_reply);
    }
});

router.post('/commands/share', (req, res, next) => {
    let payload = req.body;

    bot.share_channel(payload.team_id, payload.channel_id, payload.channel_name, (reply) => {
        res.send(reply);
    });
});

router.post('/commands/available', (req, res, next) => {
    let payload = req.body;

    bot.get_available_channels(payload.channel_id, payload.text, (reply) => {
        res.send(reply);
    });
});

export default router;<|endoftext|>


<commit_before>function sumTop10Holdings(top10Holdings) {
	let result = 0;
	top10Holdings.forEach( function(element) {
		// ["SYMBOL", "Company Ltd", "5.23%"]
		result += parseFloat(element[2].replace('%', ''));
	});
	return result;
}

exports.filter = (data) => {
	data.forEach( function(element) {;
		element.pePb = (element.pe * element.pb).toFixed(1);
		element.forecastPePb = (element.forecastPe * element.forecastPb).toFixed(1);
		element.percentageTop10 = sumTop10Holdings(element.top10Holdings);
		delete element.stockStyleExposure;
		delete element.stockStyleExposure;
		delete element.sectors;
		delete element.marketCapitalizations;
		delete element.regions;
		delete element.top10Holdings;
	});
	return Promise.resolve(data);
}<commit_msg>Minor change<commit_after>function sumTop10Holdings(top10Holdings) {
	let result = 0;
	top10Holdings.forEach( function(element) {
		// ["SYMBOL", "Company Ltd", "5.23%"]
		result += parseFloat(element[2].replace('%', '')).toFixed(2);
	});
	return result;
}

exports.filter = (data) => {
	data.forEach( function(element) {;
		element.pePb = (element.pe * element.pb).toFixed(1);
		element.forecastPePb = (element.forecastPe * element.forecastPb).toFixed(1);
		element.percentageTop10 = sumTop10Holdings(element.top10Holdings);
		delete element.stockStyleExposure;
		delete element.stockStyleExposure;
		delete element.sectors;
		delete element.marketCapitalizations;
		delete element.regions;
		delete element.top10Holdings;
	});
	return Promise.resolve(data);
}<|endoftext|>