<commit_before>import ClipperLib from 'clipper-lib';

let errorCallback;
export const setErrorCallback = (callback) => {
  errorCallback = callback;
}
ClipperLib.Error = (message) => {
  if (errorCallback) errorCallback(message);
}

const CLIPPER = new ClipperLib.Clipper();
const CLIPPER_OFFSET = new ClipperLib.ClipperOffset();

export default class Shape {
  constructor(paths = [], closed = true, capitalConversion = false, integerConversion = false, removeDuplicates = false) {
    this.paths = paths;
    if (capitalConversion) this.paths = this.paths.map(mapLowerToCapital);
    if (integerConversion) this.paths = this.paths.map(mapToRound);
    if (removeDuplicates) this.paths = this.paths.map(filterPathsDuplicates);
    this.closed = closed;
  }

  _clip(type, ...clipShapes) {
    const solution = new ClipperLib.PolyTree();

    CLIPPER.Clear();
    CLIPPER.AddPaths(this.paths, ClipperLib.PolyType.ptSubject, this.closed);
    for (let i = 0; i < clipShapes.length; i ++) {
      const clipShape = clipShapes[i];
      CLIPPER.AddPaths(clipShape.paths, ClipperLib.PolyType.ptClip, clipShape.closed);
    }
    CLIPPER.Execute(type, solution);

    const newShape = ClipperLib.Clipper.PolyTreeToPaths(solution);
    return new Shape(newShape, this.closed);
  }

  union(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctUnion, ...clipShapes);
  }

  difference(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctDifference, ...clipShapes);
  }

  intersect(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctIntersection, ...clipShapes);
  }

  xor(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctXor, ...clipShapes);
  }

  offset(offset, options = {}) {
    const {
      jointType = 'jtSquare',
      endType = 'etClosedPolygon',
      miterLimit = 2.0,
      roundPrecision = 0.25
    } = options;

    CLIPPER_OFFSET.Clear();
    CLIPPER_OFFSET.ArcTolerance = roundPrecision;
    CLIPPER_OFFSET.MiterLimit = miterLimit;

    const offsetPaths = new ClipperLib.Paths();
    CLIPPER_OFFSET.AddPaths(this.paths, ClipperLib.JoinType[jointType], ClipperLib.EndType[endType]);
    CLIPPER_OFFSET.Execute(offsetPaths, offset);

    return new Shape(offsetPaths, true);
  }

  scaleUp(factor) {
    ClipperLib.JS.ScaleUpPaths(this.paths, factor);

    return this;
  }

  scaleDown(factor) {
    ClipperLib.JS.ScaleDownPaths(this.paths, factor);

    return this;
  }

  firstPoint(toLower = false) {
    if (this.paths.length === 0) {
      return;
    }

    const firstPath = this.paths[0];
    const firstPoint = firstPath[0];
    if (toLower) {
      return vectorToLower(firstPoint);
    } else {
      return firstPoint;
    }
  }

  lastPoint(toLower = false) {
    if (this.paths.length === 0) {
      return;
    }

    const lastPath = this.paths[this.paths.length - 1];
    const lastPoint = this.closed ? lastPath[0] : lastPath[lastPath.length - 1];
    if (toLower) {
      return vectorToLower(lastPoint);
    } else {
      return lastPoint;
    }
  }

  areas() {
    const areas = this.paths.map((path, i) => this.area(i));
    return areas;
  }

  area(index) {
    const path = this.paths[index];
    const area = ClipperLib.Clipper.Area(path);
    return area;
  }

  totalArea() {
    return this.areas().reduce((totalArea, area) => totalArea + area, 0);
  }

  perimeter(index) {
    const path = this.paths[index];
    const perimeter = ClipperLib.JS.PerimeterOfPath(path, this.closed, 1);
    return perimeter;
  }

  perimeters() {
    return this.paths.map(path => ClipperLib.JS.PerimeterOfPath(path, this.closed, 1));
  }

  totalPerimeter() {
    const perimeter = ClipperLib.JS.PerimeterOfPaths(this.paths, this.closed);
    return perimeter;
  }

  reverse() {
    ClipperLib.Clipper.ReversePaths(this.paths);

    return this;
  }

  thresholdArea(minArea) {
    // code not tested yet
    for (const path of [...this.paths]) {
      const area = Math.abs(ClipperLib.Clipper.Area(shape));

      if (area < minArea) {
        const index = this.paths.indexOf(path);
        this.splice(index, 1);
      }
    }
  }

  join(shape) {
    this.paths.splice(this.paths.length, 0, ...shape.paths);

    return this;
  }

  clone() {
    return new Shape(ClipperLib.JS.Clone(this.paths), this.closed);
  }

  shapeBounds() {
    return ClipperLib.JS.BoundsOfPaths(this.paths);
  }

  pathBounds(index) {
    const path = this.paths[index];

    return ClipperLib.JS.BoundsOfPath(path);
  }

  clean(cleanDelta) {
    return new Shape(ClipperLib.Clipper.CleanPolygons(this.paths, cleanDelta), this.closed);
  }

  orientation(index) {
    const path = this.paths[index];
    return ClipperLib.Clipper.Orientation(path);
  }

  pointInShape(point, capitalConversion = false, integerConversion = false) {
    if (capitalConversion) point = vectorToCapital(point);
    if (integerConversion) point = roundVector(point);
    for (let i = 0; i < this.paths.length; i ++) {
      const pointInPath = this.pointInPath(i, point);
      const orientation = this.orientation(i);

      if ((!pointInPath && orientation) || (pointInPath && !orientation)) {
        return false;
      }
    }

    return true;
  }

  pointInPath(index, point, capitalConversion = false, integerConversion = false) {
    if (capitalConversion) point = vectorToCapital(point);
    if (integerConversion) point = roundVector(point);
    const path = this.paths[index];
    const intPoint = { X: Math.round(point.X), Y: Math.round(point.Y) };

    return ClipperLib.Clipper.PointInPolygon(intPoint, path) > 0;
  }

  fixOrientation() {
    if (!this.closed) {
      return this;
    }

    if (this.totalArea() < 0) {
      this.reverse();
    }

    return this;
  }

  simplify(fillType) {
    if (this.closed) {
      const shape = ClipperLib.Clipper.SimplifyPolygons(this.paths, ClipperLib.PolyFillType[fillType]);
      return new Shape(shape, true);
    } else {
      return this;
    }
  }

  seperateShapes() {
    const shapes = [];

    if (!this.closed) {
      for (const path of this.paths) {
        shapes.push(new Shape([path], false));
      }
    } else {
      const areas = new WeakMap();
      const outlines = [];
      const holes = [];

      for (let i = 0; i < this.paths.length; i ++) {
        const path = this.paths[i];
        const orientation = this.orientation(i);

        if (orientation) {
          const area = this.area(i);
          areas.set(path, area);
          outlines.push(path);
        } else {
          holes.push(path);
        }
      }

      outlines.sort((a, b) => areas.get(a) - areas.get(b));

      for (const outline of outlines) {
        const shape = [outline];

        const index = this.paths.indexOf(outline);

        for (const hole of [...holes]) {
          const pointInHole = this.pointInPath(index, hole[0]);
          if (pointInHole) {
            shape.push(hole);

            const index = holes.indexOf(hole);
            holes.splice(index, 1);
          }
        }

        shapes.push(new Shape(shape, true));
      }
    }

    return shapes;
  }

  round() {
    return new Shape(this.paths.map(mapToRound), this.closed);
  }

  removeDuplicates() {
    return new Shape(this.paths.map(filterPathsDuplicates), this.closed);
  }

  mapToLower() {
    return this.paths.map(mapCapitalToLower);
  }
}

function mapCapitalToLower(path) {
  return path.map(vectorToLower);
}

function vectorToLower({ X, Y }) {
  return { x: X, y: Y };
}

function mapLowerToCapital(path) {
  return path.map(vectorToCapital);
}

function vectorToCapital({ x, y }) {
  return new ClipperLib.IntPoint(x, y);
}

function mapToRound(path) {
  return path.map(roundVector);
}

function roundVector({ X, Y }) {
  return new ClipperLib.IntPoint(Math.round(X), Math.round(Y));
}

function filterPathsDuplicates(path) {
  return path.filter(filterPathDuplicates);
}

function filterPathDuplicates(point, i, array) {
  if (i === 0) return true;

  const prevPoint = array[i - 1];
  return !(point.X === prevPoint.X && point.Y === prevPoint.Y);
}
<commit_msg>fix error ClipperLib.IntPoint is not a constructor<commit_after>import ClipperLib from 'clipper-lib';

let errorCallback;
export const setErrorCallback = (callback) => {
  errorCallback = callback;
}
ClipperLib.Error = (message) => {
  if (errorCallback) errorCallback(message);
}

const CLIPPER = new ClipperLib.Clipper();
const CLIPPER_OFFSET = new ClipperLib.ClipperOffset();

export default class Shape {
  constructor(paths = [], closed = true, capitalConversion = false, integerConversion = false, removeDuplicates = false) {
    this.paths = paths;
    if (capitalConversion) this.paths = this.paths.map(mapLowerToCapital);
    if (integerConversion) this.paths = this.paths.map(mapToRound);
    if (removeDuplicates) this.paths = this.paths.map(filterPathsDuplicates);
    this.closed = closed;
  }

  _clip(type, ...clipShapes) {
    const solution = new ClipperLib.PolyTree();

    CLIPPER.Clear();
    CLIPPER.AddPaths(this.paths, ClipperLib.PolyType.ptSubject, this.closed);
    for (let i = 0; i < clipShapes.length; i ++) {
      const clipShape = clipShapes[i];
      CLIPPER.AddPaths(clipShape.paths, ClipperLib.PolyType.ptClip, clipShape.closed);
    }
    CLIPPER.Execute(type, solution);

    const newShape = ClipperLib.Clipper.PolyTreeToPaths(solution);
    return new Shape(newShape, this.closed);
  }

  union(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctUnion, ...clipShapes);
  }

  difference(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctDifference, ...clipShapes);
  }

  intersect(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctIntersection, ...clipShapes);
  }

  xor(...clipShapes) {
    return this._clip(ClipperLib.ClipType.ctXor, ...clipShapes);
  }

  offset(offset, options = {}) {
    const {
      jointType = 'jtSquare',
      endType = 'etClosedPolygon',
      miterLimit = 2.0,
      roundPrecision = 0.25
    } = options;

    CLIPPER_OFFSET.Clear();
    CLIPPER_OFFSET.ArcTolerance = roundPrecision;
    CLIPPER_OFFSET.MiterLimit = miterLimit;

    const offsetPaths = new ClipperLib.Paths();
    CLIPPER_OFFSET.AddPaths(this.paths, ClipperLib.JoinType[jointType], ClipperLib.EndType[endType]);
    CLIPPER_OFFSET.Execute(offsetPaths, offset);

    return new Shape(offsetPaths, true);
  }

  scaleUp(factor) {
    ClipperLib.JS.ScaleUpPaths(this.paths, factor);

    return this;
  }

  scaleDown(factor) {
    ClipperLib.JS.ScaleDownPaths(this.paths, factor);

    return this;
  }

  firstPoint(toLower = false) {
    if (this.paths.length === 0) {
      return;
    }

    const firstPath = this.paths[0];
    const firstPoint = firstPath[0];
    if (toLower) {
      return vectorToLower(firstPoint);
    } else {
      return firstPoint;
    }
  }

  lastPoint(toLower = false) {
    if (this.paths.length === 0) {
      return;
    }

    const lastPath = this.paths[this.paths.length - 1];
    const lastPoint = this.closed ? lastPath[0] : lastPath[lastPath.length - 1];
    if (toLower) {
      return vectorToLower(lastPoint);
    } else {
      return lastPoint;
    }
  }

  areas() {
    const areas = this.paths.map((path, i) => this.area(i));
    return areas;
  }

  area(index) {
    const path = this.paths[index];
    const area = ClipperLib.Clipper.Area(path);
    return area;
  }

  totalArea() {
    return this.areas().reduce((totalArea, area) => totalArea + area, 0);
  }

  perimeter(index) {
    const path = this.paths[index];
    const perimeter = ClipperLib.JS.PerimeterOfPath(path, this.closed, 1);
    return perimeter;
  }

  perimeters() {
    return this.paths.map(path => ClipperLib.JS.PerimeterOfPath(path, this.closed, 1));
  }

  totalPerimeter() {
    const perimeter = ClipperLib.JS.PerimeterOfPaths(this.paths, this.closed);
    return perimeter;
  }

  reverse() {
    ClipperLib.Clipper.ReversePaths(this.paths);

    return this;
  }

  thresholdArea(minArea) {
    // code not tested yet
    for (const path of [...this.paths]) {
      const area = Math.abs(ClipperLib.Clipper.Area(shape));

      if (area < minArea) {
        const index = this.paths.indexOf(path);
        this.splice(index, 1);
      }
    }
  }

  join(shape) {
    this.paths.splice(this.paths.length, 0, ...shape.paths);

    return this;
  }

  clone() {
    return new Shape(ClipperLib.JS.Clone(this.paths), this.closed);
  }

  shapeBounds() {
    return ClipperLib.JS.BoundsOfPaths(this.paths);
  }

  pathBounds(index) {
    const path = this.paths[index];

    return ClipperLib.JS.BoundsOfPath(path);
  }

  clean(cleanDelta) {
    return new Shape(ClipperLib.Clipper.CleanPolygons(this.paths, cleanDelta), this.closed);
  }

  orientation(index) {
    const path = this.paths[index];
    return ClipperLib.Clipper.Orientation(path);
  }

  pointInShape(point, capitalConversion = false, integerConversion = false) {
    if (capitalConversion) point = vectorToCapital(point);
    if (integerConversion) point = roundVector(point);
    for (let i = 0; i < this.paths.length; i ++) {
      const pointInPath = this.pointInPath(i, point);
      const orientation = this.orientation(i);

      if ((!pointInPath && orientation) || (pointInPath && !orientation)) {
        return false;
      }
    }

    return true;
  }

  pointInPath(index, point, capitalConversion = false, integerConversion = false) {
    if (capitalConversion) point = vectorToCapital(point);
    if (integerConversion) point = roundVector(point);
    const path = this.paths[index];
    const intPoint = { X: Math.round(point.X), Y: Math.round(point.Y) };

    return ClipperLib.Clipper.PointInPolygon(intPoint, path) > 0;
  }

  fixOrientation() {
    if (!this.closed) {
      return this;
    }

    if (this.totalArea() < 0) {
      this.reverse();
    }

    return this;
  }

  simplify(fillType) {
    if (this.closed) {
      const shape = ClipperLib.Clipper.SimplifyPolygons(this.paths, ClipperLib.PolyFillType[fillType]);
      return new Shape(shape, true);
    } else {
      return this;
    }
  }

  seperateShapes() {
    const shapes = [];

    if (!this.closed) {
      for (const path of this.paths) {
        shapes.push(new Shape([path], false));
      }
    } else {
      const areas = new WeakMap();
      const outlines = [];
      const holes = [];

      for (let i = 0; i < this.paths.length; i ++) {
        const path = this.paths[i];
        const orientation = this.orientation(i);

        if (orientation) {
          const area = this.area(i);
          areas.set(path, area);
          outlines.push(path);
        } else {
          holes.push(path);
        }
      }

      outlines.sort((a, b) => areas.get(a) - areas.get(b));

      for (const outline of outlines) {
        const shape = [outline];

        const index = this.paths.indexOf(outline);

        for (const hole of [...holes]) {
          const pointInHole = this.pointInPath(index, hole[0]);
          if (pointInHole) {
            shape.push(hole);

            const index = holes.indexOf(hole);
            holes.splice(index, 1);
          }
        }

        shapes.push(new Shape(shape, true));
      }
    }

    return shapes;
  }

  round() {
    return new Shape(this.paths.map(mapToRound), this.closed);
  }

  removeDuplicates() {
    return new Shape(this.paths.map(filterPathsDuplicates), this.closed);
  }

  mapToLower() {
    return this.paths.map(mapCapitalToLower);
  }
}

function mapCapitalToLower(path) {
  return path.map(vectorToLower);
}

function vectorToLower({ X, Y }) {
  return { x: X, y: Y };
}

function mapLowerToCapital(path) {
  return path.map(vectorToCapital);
}

function vectorToCapital({ x, y }) {
  return { X: x, Y: y };
}

function mapToRound(path) {
  return path.map(roundVector);
}

function roundVector({ X, Y }) {
  return new { X: Math.round(X), Y: Math.round(Y) };
}

function filterPathsDuplicates(path) {
  return path.filter(filterPathDuplicates);
}

function filterPathDuplicates(point, i, array) {
  if (i === 0) return true;

  const prevPoint = array[i - 1];
  return !(point.X === prevPoint.X && point.Y === prevPoint.Y);
}
<|endoftext|>


<commit_before>;(function() {
  'use strict'

  var instances = []
  var lastUsedColorIndex = 0
  var nextColorIndex
  // Solarized accent colors http://ethanschoonover.com/solarized
  var colors = [
    '#B58900',
    '#CB4B16',
    '#DC322F',
    '#D33682',
    '#6C71C4',
    '#268BD2',
    '#2AA198',
    '#859900'
  ]
  var disabledInstancesRegExps = []
  var enabledInstancesRegExps = [] // RegExp that matches nothing

  function Logdown(opts) {
    // enforces new
    if (!(this instanceof Logdown)) {
      return new Logdown(opts)
    }

    //
    opts = opts || {}
    this.markdown = opts.markdown || true
    this.prefix = opts.prefix || ''

    //
    instances.push(this)

    this.prefixColor = colors[lastUsedColorIndex++ % colors.length]

    return this
  }

  // Static
  // ------

  Logdown.enable = function(str) {
    if (str === '*') {
      disabledInstancesRegExps = []
    }
    var regExp = prepareRegExpForPrefixSearch(str)
    enabledInstancesRegExps.push(regExp)
  }

  Logdown.disable = function(str) {
    if (str === '*') {
      enabledInstancesRegExps = []
    }
    var regExp = prepareRegExpForPrefixSearch(str)
    disabledInstancesRegExps.push(regExp)
  }

  // Public
  // ------

  Logdown.prototype.log = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.log.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.info = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.info.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.error = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.error.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.warn = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.warn.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  // Private
  // -------

  function parseMarkdown(text) {
    var styles = []
    var match = getNextMatch(text)

    while (match) {
      text = text.replace(match.rule.regexp, match.rule.replacer)
      styles.push(match.rule.style)
      styles.push('')

      match = getNextMatch(text)
    }

    return {text: text, styles: styles}
  }

  function getNextMatch(text) {
    var matches = []
    var rules = [
      {
        regexp: /\*([^\*]+)\*/,
        replacer: function(match, submatch1) {
          return '%c' + submatch1 + '%c'
        },
        style: 'font-weight: bold;'
      },
      {
        regexp: /\_([^\_]+)\_/,
        replacer: function(match, submatch1) {
          return '%c' + submatch1 + '%c'
        },
        style: 'font-style: italic;'
      },
      {
        regexp: /\`([^\`]+)\`/,
        replacer: function(match, submatch1) {
          return "%c" + submatch1 + "%c";
        },
        style:
          'background: #FDF6E3;' +
          'color: #586E75;' +
          'padding: 1px 5px;' +
          'border-radius: 4px;'
      }
    ]

    //
    rules.forEach(function(rule) {
      var match = text.match(rule.regexp)
      if (match) {
        matches.push({
          rule: rule,
          match: match
        })
      }
    })
    if (matches.length === 0) {
      return null;
    }

    //
    // matches.sort(function(a, b) {
    //   return a.match.index - b.match.index
    // })

    return matches[0]
  }

  function prepareOutput(text, instance) {
    var parsedMarkdown
    var parsedText
    var styles

    if (instance.markdown) {
      parsedMarkdown = parseMarkdown(text)
      parsedText = parsedMarkdown.text
      styles = parsedMarkdown.styles
    }

    parsedText = parsedText || text
    styles = styles || []

    if (instance.prefix) {
      parsedText = '%c' + instance.prefix + '%c ' + parsedText
      styles.unshift('color:' + instance.prefixColor + '; font-weight:bold;', '')
    }

    return {
      parsedText: parsedText,
      styles: styles
    }
  }

  function isDisabled(instance) {
    var isDisabled = false
    var isEnabled = false

    disabledInstancesRegExps.forEach(function(regExp) {
      if (regExp.test(instance.prefix)) {
        isDisabled = true
        return
      }
    })

    enabledInstancesRegExps.forEach(function(regExp) {
      if (regExp.test(instance.prefix)) {
        isEnabled = true
        return
      }
    })

    return (isDisabled && !isEnabled)
  }

  function prepareRegExpForPrefixSearch(str) {
    return new RegExp('^' + str.replace(/\*/g, '.*?') + '$')
  }

  // FIX: do an export the right way
  if (module && module.exports) {
    module.exports = Logdown
  } else {
    window.Logdown = Logdown
  }

}())
<commit_msg>Fix bug on disable/enable<commit_after>;(function() {
  'use strict'

  var instances = []
  var lastUsedColorIndex = 0
  var nextColorIndex
  // Solarized accent colors http://ethanschoonover.com/solarized
  var colors = [
    '#B58900',
    '#CB4B16',
    '#DC322F',
    '#D33682',
    '#6C71C4',
    '#268BD2',
    '#2AA198',
    '#859900'
  ]
  var filterRegExps = []

  function Logdown(opts) {
    // enforces new
    if (!(this instanceof Logdown)) {
      return new Logdown(opts)
    }

    //
    opts = opts || {}
    this.markdown = opts.markdown || true
    this.prefix = opts.prefix || ''

    //
    instances.push(this)

    this.prefixColor = colors[lastUsedColorIndex++ % colors.length]

    return this
  }

  // Static
  // ------

  Logdown.enable = function(str) {
    var regExp = prepareRegExpForPrefixSearch(str)

    if (str === '*') {
      filterRegExps = [{
        type: 'enable',
        regExp: regExp
      }]
    } else {
      filterRegExps.push({
        type: 'enable',
        regExp: regExp
      })
    }
  }

  Logdown.disable = function(str) {
    var regExp = prepareRegExpForPrefixSearch(str)

    if (str === '*') {
      filterRegExps = [{
        type: 'disable',
        regExp: regExp
      }]
    } else {
      filterRegExps.push({
        type: 'disable',
        regExp: regExp
      })
    }
  }

  // Public
  // ------

  Logdown.prototype.log = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.log.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.info = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.info.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.error = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.error.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  Logdown.prototype.warn = function(text) {
    var preparedOutput

    if (isDisabled(this)) {
      return
    }

    preparedOutput = prepareOutput(text, this)

    console.warn.apply(
      console,
      [preparedOutput.parsedText].concat(preparedOutput.styles)
    )
  }

  // Private
  // -------

  function parseMarkdown(text) {
    var styles = []
    var match = getNextMatch(text)

    while (match) {
      text = text.replace(match.rule.regexp, match.rule.replacer)
      styles.push(match.rule.style)
      styles.push('')

      match = getNextMatch(text)
    }

    return {text: text, styles: styles}
  }

  function getNextMatch(text) {
    var matches = []
    var rules = [
      {
        regexp: /\*([^\*]+)\*/,
        replacer: function(match, submatch1) {
          return '%c' + submatch1 + '%c'
        },
        style: 'font-weight: bold;'
      },
      {
        regexp: /\_([^\_]+)\_/,
        replacer: function(match, submatch1) {
          return '%c' + submatch1 + '%c'
        },
        style: 'font-style: italic;'
      },
      {
        regexp: /\`([^\`]+)\`/,
        replacer: function(match, submatch1) {
          return "%c" + submatch1 + "%c";
        },
        style:
          'background: #FDF6E3;' +
          'color: #586E75;' +
          'padding: 1px 5px;' +
          'border-radius: 4px;'
      }
    ]

    //
    rules.forEach(function(rule) {
      var match = text.match(rule.regexp)
      if (match) {
        matches.push({
          rule: rule,
          match: match
        })
      }
    })
    if (matches.length === 0) {
      return null;
    }

    //
    // matches.sort(function(a, b) {
    //   return a.match.index - b.match.index
    // })

    return matches[0]
  }

  function prepareOutput(text, instance) {
    var parsedMarkdown
    var parsedText
    var styles

    if (instance.markdown) {
      parsedMarkdown = parseMarkdown(text)
      parsedText = parsedMarkdown.text
      styles = parsedMarkdown.styles
    }

    parsedText = parsedText || text
    styles = styles || []

    if (instance.prefix) {
      parsedText = '%c' + instance.prefix + '%c ' + parsedText
      styles.unshift('color:' + instance.prefixColor + '; font-weight:bold;', '')
    }

    return {
      parsedText: parsedText,
      styles: styles
    }
  }

  function isDisabled(instance) {
    var isDisabled = false

    filterRegExps.forEach(function(filter) {
      if (filter.type === 'enable' && filter.regExp.test(instance.prefix)) {
        isDisabled = false
      } else if (filter.type === 'disable' && filter.regExp.test(instance.prefix)) {
        isDisabled = true
      }
    })

    return isDisabled
  }

  function prepareRegExpForPrefixSearch(str) {
    return new RegExp('^' + str.replace(/\*/g, '.*?') + '$')
  }

  // FIX: do an export the right way
  if (module && module.exports) {
    module.exports = Logdown
  } else {
    window.Logdown = Logdown
  }

}())
<|endoftext|>


<commit_before>/*
 * Antinit base lib
 */
import Layer from './layer'
import groupsLevel from './groups_levels'
import Keeper from './plugins/keeper'
import SystemGraph from './system_graph'
import legacyHelper from './legacy_helper'

const ANTINITE_SYSTEM_NAME = 'AntiniteSystem'

let AntiniteAuditor, AntiniteDebugger, AntiniteToolkit, AntiniteLegacy
let reportsState = {
  isAuditEnabled: false, // shared by refs, instantly update
  isDebuggEnabled: false
}

// helpers points
let auditorProcess = new Keeper()
let debuggerProcess = new Keeper()

// common exchange point for all (in one process)
let layersExchanger = {}

/*
 * Main lib
 */
class Antinite {
  constructor (layerName) {
    this.doCheckName(layerName)
    this.layerName = layerName
    this.layer = new Layer(layerName, {
      globalLookUp: this.globalLookUp.bind(this),
      pendingRestarter: this.pendingRestarter.bind(this),
      messagerBus: this.messagerProcessor.bind(this),
      reportsState
    })
    layersExchanger[layerName] = this.layer
  }

  /*
   * Add services
   *
   * just proxy
   */
  addServices (servicesList) {
    this.layer.addWorkers(servicesList)
    return this
  }

  /*
   * Test layer name
   *
   * Not allowed undefined, empty or doubled names
   */
  doCheckName (layerName) {
    if (layerName === undefined || layerName === null || layerName === '') {
      throw Error('Layer must be named, halt!')
    }
    if (typeof layerName !== 'string') {
      throw TypeError('Layer name must be a string')
    }
    if (layersExchanger[layerName]) {
      throw Error(`Layer |${layerName}| already exists, doubles not allowed, halt!`)
    }
  }

  /*
   * Look up for all layers
   */
  globalLookUp (callerLayer, callerName, serviceName, action) {
    let res, layer

    Object.keys(layersExchanger).some((layerName) => {
      layer = layersExchanger[layerName]
      if (layerName === this.layerName) {
        return
      }
      res = layer.serviceLookup(callerLayer, callerName, groupsLevel.other, serviceName, action)
      return res
    }, this)

    return res
  }

  /*
   * Restart resolving pended workers for all layers
   *
   * if somewere dependencies pended
   */
  pendingRestarter () {
    let layer

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (layerName === this.layerName) {
        return
      }
      layer.repeatResolving()
    }, this)
  }

  /*
   * Process messages from layers and workers
   */
  messagerProcessor (topic, message) {
    switch (topic) {
      case 'auditor':
        auditorProcess.saveMessage(message)
        return
      case 'debugger':
        debuggerProcess.saveMessage(message)
        return
      default:
        throw Error(`Topic |${topic}| not in processing list!`)
    }
  }
}

/*
 * System helper for `system` acess rights
 */
class AntiniteSystem {
  constructor (name) {
    this.name = name
    this.grantedItem = {}
  }

  /*
   * Execute command with `system` rights
   */
  execute (layerName, serviceName, action, ...args) {
    let service
    let layer = layersExchanger[layerName]
    let fullPath = this.getKeyForGrantedItems(layerName, serviceName, action)
    let grantedItem = this.grantedItem[fullPath]

    // shortcut for resolved early functions
    if (grantedItem) {
      return grantedItem.layer.executeAction(grantedItem.ticket, serviceName, action, ...args)
    }
    if (!layer) {
      throw Error(`unknown layer ${layer}`)
    }
    if (!layer.isServiceRegistered(serviceName)) {
      throw Error(`unknown service |${layer.getName()}.${serviceName}|`)
    }
    service = layer.serviceLookup('system', this.getName(), groupsLevel.system, serviceName, action)
    if (!service) {
      throw Error(`cant access action |${layer.getName()}.${serviceName}.${action}|`)
    }
    this.grantedItem[fullPath] = service
    return layer.executeAction(service.ticket, serviceName, action, ...args)
  }

  /*
   * Check ALL requres resolved in ALL layers
   */
  ensureAllIsReady () {
    let layer

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (!layer.isReady()) {
        throw Error(`Layer |${layerName}| not ready, halt!`)
      }
    }, this)
  }

  /*
   * Return all unresolved
   */
  getUnreadyList () {
    let layer, services, service
    let result = []

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (!layer.isReady()) {
        services = layer.getServices()
        Object.keys(services).forEach((serviceName) => {
          service = services[serviceName]
          if (!service.isReady()) {
            result.push(`${layer.getName()}.${service.getName()} at ${JSON.stringify(service.requirePending)}`)
          }
        }, this)
      }
    }, this)

    return result
  }

  /*
   * Return current system service name
   */
  getName () {
    if (this.name) {
      return this.name
    } else {
      return ANTINITE_SYSTEM_NAME
    }
  }

  /*
   * Make one-string text key presentation
   *
   * to speed up granted acess look up in flat dictionary
   */
  getKeyForGrantedItems (layerName, serviceName, action) {
    return `${layerName}|${serviceName}|${action}`
  }
}

/*
 * Legacy helper
 *
 * not a class, do not use at production
 */
AntiniteLegacy = {
  /*
   * Register service
   */
  register: (options) => {
    // fast check options
    legacyHelper.checkOptions(options)
    if (!layersExchanger[options.layer]) {
      debuggerProcess.saveMessage({message: `Create LEGACY layer |${options.layer}|`})
      new Antinite(options.layer) // eslint-disable-line no-new
      layersExchanger[options.layer].markAsLegacy()
    }
    debuggerProcess.saveMessage({message: `Add LEGACY service |${options.layer}.${options.name}|`})
    // add legacy service
    options.isLegacy = true
    layersExchanger[options.layer].addWorkers([options])
  }
}

/*
 * Audit log point
 *
 * not a class, system wide
 */
AntiniteAuditor = {
  /*
   * Audit enabler/disabler
   */
  setMode: (isOn) => {
    reportsState.isAuditEnabled = isOn
  },

  /*
   * Get audit data
   */
  getData: () => {
    return auditorProcess.getMessages()
  },

  /*
   * Setup audit log size
   */
  setLogSize: (size) => {
    auditorProcess.setMaxStorageSize(size)
  }
}

/*
 * Debug log point
 *
 * not a class, system wide
 */
AntiniteDebugger = {
  /*
   * Debug enabler/disabler
   */
  setMode: (isOn) => {
    reportsState.isDebuggEnabled = isOn
  },

  /*
   * Get debugger data
   */
  getData: () => {
    return debuggerProcess.getMessages()
  },

  /*
   * Setup debugger log size
   */
  setLogSize: (size) => {
    debuggerProcess.setMaxStorageSize(size)
  }
}

/*
 * Toolkit for tests only!!!
 *
 * DO NOT USE IT IN WORKING CODE
 */
AntiniteToolkit = {
  /*
   * Wipe all Antinite state
   *
   * !!!use it for test only!!!
   * IMPORTANT - System cached 'execute' and if it not re-create - it may call dropped layers (it has link at granted access)
   */
  _WIPE_ALL_: () => {
    // drop reports states
    reportsState.isAuditEnabled = false
    reportsState.isDebuggEnabled = false
    // wipe loggers
    auditorProcess = new Keeper()
    debuggerProcess = new Keeper()
    // drop all layers
    layersExchanger = {}
  },

  /*
   * Return all registered services properties
   *
   * Data for all layers for all services to visualise system components
   */
  getSystemGraph: () => {
    return SystemGraph.getData(layersExchanger)
  }
}

export {
  Antinite as Layer,
  AntiniteSystem as System,
  AntiniteAuditor as Auditor,
  AntiniteDebugger as Debugger,
  AntiniteLegacy as Legacy,
  AntiniteToolkit}
<commit_msg>* add service.onReady<commit_after>/*
 * Antinit base lib
 */
import Layer from './layer'
import groupsLevel from './groups_levels'
import Keeper from './plugins/keeper'
import SystemGraph from './system_graph'
import legacyHelper from './legacy_helper'

const ANTINITE_SYSTEM_NAME = 'AntiniteSystem'
const READY_DELAY = 20 //ms, delay for 'ready' messages to trevent throttling

let AntiniteAuditor, AntiniteDebugger, AntiniteToolkit, AntiniteLegacy
let reportsState = {
  isAuditEnabled: false, // shared by refs, instantly update
  isDebuggEnabled: false
}

// helpers points
let auditorProcess = new Keeper()
let debuggerProcess = new Keeper()

// common exchange point for all (in one process)
let layersExchanger = {}
// all system instances
let systemInstances = []
// ready timers - only for WIPE
let timers = []

/*
 * Main lib
 */
class Antinite {
  constructor (layerName) {
    this.doCheckName(layerName)
    this.layerName = layerName
    this.layer = new Layer(layerName, {
      globalLookUp: this.globalLookUp.bind(this),
      pendingRestarter: this.pendingRestarter.bind(this),
      messagerBus: this.messagerProcessor.bind(this),
      reportsState
    })
    layersExchanger[layerName] = this.layer
    this.readyAwaitFlag = false
  }

  /*
   * Add services
   *
   * just proxy
   */
  addServices (servicesList) {
    this.layer.addWorkers(servicesList)
    return this
  }

  /*
   * Test layer name
   *
   * Not allowed undefined, empty or doubled names
   */
  doCheckName (layerName) {
    if (layerName === undefined || layerName === null || layerName === '') {
      throw Error('Layer must be named, halt!')
    }
    if (typeof layerName !== 'string') {
      throw TypeError('Layer name must be a string')
    }
    if (layersExchanger[layerName]) {
      throw Error(`Layer |${layerName}| already exists, doubles not allowed, halt!`)
    }
  }

  /*
   * Look up for all layers
   */
  globalLookUp (callerLayer, callerName, serviceName, action) {
    let res, layer

    Object.keys(layersExchanger).some((layerName) => {
      layer = layersExchanger[layerName]
      if (layerName === this.layerName) {
        return
      }
      res = layer.serviceLookup(callerLayer, callerName, groupsLevel.other, serviceName, action)
      return res
    }, this)

    return res
  }

  /*
   * Restart resolving pended workers for all layers
   *
   * if somewere dependencies pended
   */
  pendingRestarter () {
    let layer

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (layerName === this.layerName) {
        return
      }
      layer.repeatResolving()
    }, this)
  }

  /*
   * Process messages from layers and workers
   */
  messagerProcessor (topic, message) {
    switch (topic) {
      case 'auditor':
        auditorProcess.saveMessage(message)
        return
      case 'debugger':
        debuggerProcess.saveMessage(message)
        return
      case 'ready':
        this.readyProcessor()
        return
      default:
        throw Error(`Topic |${topic}| not in processing list!`)
    }
  }

  /*
   * Set "ready" messages from async initService to queue
   *
   * we are need delay to prevent register flud
   */
  readyProcessor () {
    if (!this.readyAwaitFlag) {
      this.readyAwaitFlag = setTimeout(() => { this.awaitReadyProcessor() }, READY_DELAY)
      timers.push(this.readyAwaitFlag)
    }
  }

  /*
   * Process "ready" messages from async initService functions
   */
  awaitReadyProcessor () {
    let isReady = false

    try {
      AntiniteSystem.prototype.ensureAllIsReady()
      isReady = true
    } catch (err) {
      // it normal, just ignore
      // console.log(err)
    }
    // if all ready - inform system
    if (isReady) {
      AntiniteSystem.prototype.sendAllReadyEvent()
    }
    this.readyAwaitFlag = false
  }
}

/*
 * System helper for `system` acess rights
 */
class AntiniteSystem {
  constructor (name) {
    this.name = name
    this.grantedItem = {}
    this.onReadyFn = false
    this.registerItselfAtList()
  }

  registerItselfAtList () {
    systemInstances.push(this)
  }

  /*
   * To wait 'ready' event
   */
  onReady (cb) {
    if (cb) {
      this.onReadyFn = cb
      return
    }
    return new Promise((resolve) => {
      this.onReadyFn = () => {
        return resolve()
      }
    })
  }

  /*
   * Execute command with `system` rights
   */
  execute (layerName, serviceName, action, ...args) {
    let service
    let layer = layersExchanger[layerName]
    let fullPath = this.getKeyForGrantedItems(layerName, serviceName, action)
    let grantedItem = this.grantedItem[fullPath]

    // shortcut for resolved early functions
    if (grantedItem) {
      return grantedItem.layer.executeAction(grantedItem.ticket, serviceName, action, ...args)
    }
    if (!layer) {
      throw Error(`unknown layer ${layer}`)
    }
    if (!layer.isServiceRegistered(serviceName)) {
      throw Error(`unknown service |${layer.getName()}.${serviceName}|`)
    }
    service = layer.serviceLookup('system', this.getName(), groupsLevel.system, serviceName, action)
    if (!service) {
      throw Error(`cant access action |${layer.getName()}.${serviceName}.${action}|`)
    }
    this.grantedItem[fullPath] = service
    return layer.executeAction(service.ticket, serviceName, action, ...args)
  }

  /*
   * Check ALL requres resolved in ALL layers
   */
  ensureAllIsReady () {
    let layer

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (!layer.isReady()) {
        throw Error(`Layer |${layerName}| not ready, halt!`)
      }
    })
  }

  /*
   * Return all unresolved
   */
  getUnreadyList () {
    let layer, services, service
    let result = []

    Object.keys(layersExchanger).forEach((layerName) => {
      layer = layersExchanger[layerName]
      if (!layer.isReady()) {
        services = layer.getServices()
        Object.keys(services).forEach((serviceName) => {
          service = services[serviceName]
          if (!service.isReady()) {
            result.push(`${layer.getName()}.${service.getName()} at ${JSON.stringify(service.requirePending)}`)
          }
        })
      }
    })

    return result
  }

  /*
   * Return current system service name
   */
  getName () {
    if (this.name) {
      return this.name
    } else {
      return ANTINITE_SYSTEM_NAME
    }
  }

  /*
   * Make one-string text key presentation
   *
   * to speed up granted acess look up in flat dictionary
   */
  getKeyForGrantedItems (layerName, serviceName, action) {
    return `${layerName}|${serviceName}|${action}`
  }

  /*
   * Inform all system instances
   */
  sendAllReadyEvent () {
    systemInstances.forEach((elem) => {
      elem.sendReadyEvent()
    })
  }

  /*
   * Inform instance is ready
   */
  sendReadyEvent () {
    if (this.onReadyFn) {
      this.onReadyFn()
    }
  }
}

/*
 * Legacy helper
 *
 * not a class, do not use at production
 */
AntiniteLegacy = {
  /*
   * Register service
   */
  register: (options) => {
    // fast check options
    legacyHelper.checkOptions(options)
    if (!layersExchanger[options.layer]) {
      debuggerProcess.saveMessage({message: `Create LEGACY layer |${options.layer}|`})
      new Antinite(options.layer) // eslint-disable-line no-new
      layersExchanger[options.layer].markAsLegacy()
    }
    debuggerProcess.saveMessage({message: `Add LEGACY service |${options.layer}.${options.name}|`})
    // add legacy service
    options.isLegacy = true
    layersExchanger[options.layer].addWorkers([options])
  }
}

/*
 * Audit log point
 *
 * not a class, system wide
 */
AntiniteAuditor = {
  /*
   * Audit enabler/disabler
   */
  setMode: (isOn) => {
    reportsState.isAuditEnabled = isOn
  },

  /*
   * Get audit data
   */
  getData: () => {
    return auditorProcess.getMessages()
  },

  /*
   * Setup audit log size
   */
  setLogSize: (size) => {
    auditorProcess.setMaxStorageSize(size)
  }
}

/*
 * Debug log point
 *
 * not a class, system wide
 */
AntiniteDebugger = {
  /*
   * Debug enabler/disabler
   */
  setMode: (isOn) => {
    reportsState.isDebuggEnabled = isOn
  },

  /*
   * Get debugger data
   */
  getData: () => {
    return debuggerProcess.getMessages()
  },

  /*
   * Setup debugger log size
   */
  setLogSize: (size) => {
    debuggerProcess.setMaxStorageSize(size)
  }
}

/*
 * Toolkit for tests only!!!
 *
 * DO NOT USE IT IN WORKING CODE
 */
AntiniteToolkit = {
  /*
   * Wipe all Antinite state
   *
   * !!!use it for test only!!!
   * IMPORTANT - System cached 'execute' and if it not re-create - it may call dropped layers (it has link at granted access)
   */
  _WIPE_ALL_: () => {
    // drop reports states
    reportsState.isAuditEnabled = false
    reportsState.isDebuggEnabled = false
    // wipe loggers
    auditorProcess = new Keeper()
    debuggerProcess = new Keeper()
    // drop all layers
    layersExchanger = {}
    // drop all system
    systemInstances = []
    // clear all timers
    timers.forEach((timer) => {
      clearTimeout(timer)
    })
  },

  /*
   * Return all registered services properties
   *
   * Data for all layers for all services to visualise system components
   */
  getSystemGraph: () => {
    return SystemGraph.getData(layersExchanger)
  }
}

export {
  Antinite as Layer,
  AntiniteSystem as System,
  AntiniteAuditor as Auditor,
  AntiniteDebugger as Debugger,
  AntiniteLegacy as Legacy,
  AntiniteToolkit}
<|endoftext|>


<commit_before>var uniqueRandomArray = require("unique-random-array");
var allThoughts = require("./thoughts.json");
module.exports = {
    random:random(),
    all:allThoughts,
    particular:particular
};

function random(){
    return uniqueRandomArray(allThoughts.startup);
}
function particular(n){
    if(n<0 || n>=allThoughts.startup.length)
        return "Index out of bound";
    else if(n == null)
        return "Index required";
    else{
        return allThoughts.startup[n];
    }
}
<commit_msg>perf(index): added more functionality to random and particular functions<commit_after>var uniqueRandomArray = require("unique-random-array");
var allThoughts = require("./thoughts.json");
module.exports = {
    random:random,
    all:allThoughts,
    particular:particular
};
function random(category){
    if(category == null) {
        var randomCategory = uniqueRandomArray(allThoughts.categories);
        var randomQuote = uniqueRandomArray(allThoughts[randomCategory()]);
        return randomQuote();
    }
    else{
        var quote = uniqueRandomArray(allThoughts[category]);
        return quote();
    }
}

function particular(category,n){
    if(n<0 || n>=allThoughts[category].length)
        return "Index out of bound";
    else if(n == null || category == null)
        return "Either index or category is missing!";
    else{
        return allThoughts[category][n];
    }
}
//var lib = require("./src/index.js");lib.particular();<|endoftext|>


<commit_before>/**
 * Carries out actions for the editable framework
 */

import { List as list, Map as map } from 'immutable';
import buildMessage from '../messageBuilder';
import { EF_SERVER_ADD_REQUESTED, EF_SUGGESTIONS_REQUESTED } from '../constants/effects';
import { rGetOverviewRows, rCalculateOverview, rProcessDataOverview } from './data/overview';
import { getGainComparisons, addPriceHistory } from './data/funds';
import {
  PAGES, LIST_PAGES, LIST_COLS_PAGES, ERROR_LEVEL_WARN, ERROR_LEVEL_ERROR
} from '../misc/const';
import {
  ERROR_MSG_BAD_DATA, ERROR_MSG_API_FAILED, ERROR_MSG_BUG_INVALID_ITEM
} from '../misc/config';
import { YMD } from '../misc/date';
import {
  uuid, getNullEditable, getAddDefaultValues, sortRowsByDate, addWeeklyAverages
} from '../misc/data';
import { rErrorMessageOpen } from './ErrorReducer';

const recalculateFundProfits = (reduction, pageIndex) => {
  const transactionsKey = LIST_COLS_PAGES[pageIndex].indexOf('transactions');
  const history = reduction.getIn(['appState', 'pages', pageIndex, 'history']);
  const oldRows = reduction.getIn(['appState', 'pages', pageIndex, 'rows']);
  const newRows = getGainComparisons(oldRows.map(row => {
    return addPriceHistory(pageIndex, row, history, row.getIn(['cols', transactionsKey]));
  }));
  return reduction.setIn(['appState', 'pages', pageIndex, 'rows'], newRows);
};

const overviewKey = PAGES.indexOf('overview');
const applyEditsOverview = (reduction, item) => {
  // update the balance for a row and recalculate overview data
  const value = item.get('value');
  const row = item.get('row');

  const newCost = reduction
  .getIn(['appState', 'pages', overviewKey, 'data', 'cost'])
  .setIn(['balance', row], value);

  const startYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'startYearMonth']);
  const endYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'endYearMonth']);
  const currentYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'currentYearMonth']);
  const futureMonths = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'futureMonths']);

  const newData = rProcessDataOverview(
    newCost, startYearMonth, endYearMonth, currentYearMonth, futureMonths);

  return reduction.setIn(['appState', 'pages', overviewKey, 'data'], newData)
  .setIn(['appState', 'pages', overviewKey, 'rows'], rGetOverviewRows(newData));
};

const applyEditsList = (reduction, item, pageIndex) => {
  // update list data in the UI
  if (item.get('row') === -1) {
    // add-item
    return reduction.setIn(['appState', 'edit', 'add', item.get('col')], item.get('value'));
  }

  // update row
  const newRow = reduction.getIn(
    ['appState', 'pages', pageIndex, 'rows', item.get('row')])
    .setIn(['cols', item.get('col')], item.get('value'));

  let newReduction = reduction.setIn(
    ['appState', 'pages', pageIndex, 'rows', item.get('row')], newRow);

  // recalculate total if the cost has changed
  if (item.get('item') === 'cost') {
    newReduction = newReduction.setIn(
      ['appState', 'pages', pageIndex, 'data', 'total'],
      newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'total']) +
        item.get('value') - item.get('originalValue')
    );
  }

  // recalculate fund profits / losses if transactions have changed
  if (PAGES[pageIndex] === 'funds' && item.get('item') === 'transactions') {
    newReduction = recalculateFundProfits(newReduction, pageIndex);
  }

  // sort rows by date
  const sortedRows = sortRowsByDate(
    newReduction.getIn(['appState', 'pages', pageIndex, 'rows']), pageIndex);
  const weeklyData = addWeeklyAverages(
    newReduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  newReduction = newReduction.setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData);

  // recalculate overview data if the cost or date changed
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    if (item.get('item') === 'cost') {
      const dateKey = LIST_COLS_PAGES[pageIndex].indexOf('date');
      const date = newReduction.getIn(
        ['appState', 'pages', pageIndex, 'rows', item.get('row'), 'cols', dateKey]);

      newReduction = rCalculateOverview(
        newReduction, pageIndex, date, date, item.get('value'), item.get('originalValue'));
    }
    else if (item.get('item') === 'date') {
      const costKey = LIST_COLS_PAGES[pageIndex].indexOf('cost');
      const cost = newRow.getIn(['cols', costKey]);

      newReduction = rCalculateOverview(
        newReduction, pageIndex, item.get('value'), item.get('originalValue'), cost, cost);
    }
  }

  return newReduction;
};

/**
 * applyEdits: apply editItem edits to UI (API handled separately)
 * @param {Record} reduction: reduction to modify and return
 * @param {map} item: edit item
 * @param {integer} pageIndex: index of the page on which edits are being done
 * @returns {Record} modified reduction
 */
const applyEdits = (reduction, item, pageIndex) => {
  if (pageIndex === 0) {
    return applyEditsOverview(reduction, item);
  }
  if (LIST_PAGES.indexOf(pageIndex) > -1) {
    return applyEditsList(reduction, item, pageIndex);
  }
  return reduction;
};

export const rActivateEditable = (reduction, editable, cancel) => {
  const active = reduction.getIn(['appState', 'edit', 'active']);
  const queue = reduction.getIn(['appState', 'edit', 'queue']);
  const pageIndex = reduction.getIn(['appState', 'currentPageIndex']);
  let newReduction = reduction
  .setIn(['appState', 'edit', 'addBtnFocus'], false)
  .setIn(['appState', 'edit', 'suggestions', 'list'], list.of())
  .setIn(['appState', 'edit', 'suggestions', 'active'], -1)
  .setIn(['appState', 'edit', 'suggestions', 'loading'], false)
  .setIn(['appState', 'edit', 'suggestions', 'reqId'], null);

  // confirm the previous item's edits
  if (active && active.get('value') !== active.get('originalValue')) {
    if (cancel) {
      // revert to previous state
      newReduction = applyEdits(newReduction, active.set('value', active.get('originalValue')));
    }
    else {
      if (active.get('row') > -1) {
        // add last item to queue for saving on API
        newReduction = newReduction.setIn(['appState', 'edit', 'queue'], queue.push(active));
      }

      // append the changes of the last item to the UI
      newReduction = applyEdits(newReduction, active, pageIndex);
    }
  }

  // can pass null to deactivate editing
  if (!editable) {
    return newReduction.setIn(['appState', 'edit', 'active'], getNullEditable(pageIndex));
  }

  return newReduction.setIn(
    ['appState', 'edit', 'active'],
    editable.set('originalValue', editable.get('value'))
  );
};

export const rChangeEditable = (reduction, value) => {
  return reduction.setIn(['appState', 'edit', 'active', 'value'], value);
};

export const rDeleteListItem = (reduction, item) => {
  let newReduction = reduction;

  const pageIndex = item.pageIndex;
  const id = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'id']);
  const dateKey = LIST_COLS_PAGES[pageIndex].indexOf('date');
  const costKey = LIST_COLS_PAGES[pageIndex].indexOf('cost');
  const itemCost = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'cols', costKey]);

  // recalculate total
  newReduction = newReduction.setIn(
    ['appState', 'pages', pageIndex, 'data', 'total'],
    newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'total']) - itemCost
  );
  // sort rows and recalculate weekly data
  const sortedRows = sortRowsByDate(
    newReduction.getIn(['appState', 'pages', pageIndex, 'rows']).splice(item.key, 1), pageIndex);
  const weeklyData = addWeeklyAverages(
    newReduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  // recalculate overview data
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    const date = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'cols', dateKey]);
    newReduction = rCalculateOverview(newReduction, pageIndex, date, date, 0, itemCost);
  }

  newReduction = newReduction.setIn(
    ['appState', 'edit', 'queueDelete'],
    reduction.getIn(['appState', 'edit', 'queueDelete']).push({ pageIndex, id })
  )
  .setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData);

  // recalculate fund profits / losses
  if (PAGES[pageIndex] === 'funds') {
    newReduction = recalculateFundProfits(newReduction, pageIndex);
  }

  return newReduction;
};

export const rAddListItem = (reduction, items) => {
  if (reduction.getIn(['appState', 'loadingApi'])) {
    return reduction;
  }

  // validate items
  const active = reduction.getIn(['appState', 'edit', 'active']);
  let activeItem = null;
  let activeValue = null;
  if (active && active.get('row') === -1) {
    activeItem = active.get('item');
    activeValue = active.get('value');
  }

  const theItems = items.map(column => {
    const item = column.props.item;
    const value = item === activeItem ? activeValue : column.props.value;

    return { item, value };
  });

  const valid = theItems.reduce((a, b) => {
    const thisValid = b.item === 'item' ? b.value.length > 0 : true; // others are self-validating
    return thisValid ? a : false;
  }, true);

  if (!valid) {
    return rErrorMessageOpen(reduction, map({
      level: ERROR_LEVEL_WARN,
      text: ERROR_MSG_BAD_DATA
    }));
  }

  const item = {};
  theItems.forEach(thisItem => {
    item[thisItem.item] = thisItem.value.toString();
  });

  const apiKey = reduction.getIn(['appState', 'user', 'apiKey']);
  const pageIndex = reduction.getIn(['appState', 'currentPageIndex']);
  const req = { apiKey, item, theItems, pageIndex };

  return rActivateEditable(reduction, null)
  .setIn(['appState', 'edit', 'add'], list.of())
  .setIn(['appState', 'loadingApi'], true)
  .set('effects', reduction.get('effects').push(buildMessage(EF_SERVER_ADD_REQUESTED, req)));
};

export const rHandleServerAdd = (reduction, response) => {
  // handle the response from adding an item to a list page
  let newReduction = reduction.setIn(['appState', 'loadingApi'], false);
  if (response.response.data.error) {
    return rErrorMessageOpen(newReduction, map({
      level: ERROR_LEVEL_ERROR,
      text: `${ERROR_MSG_API_FAILED}: ${response.response.data.errorText}`
    }));
  }
  const pageIndex = response.pageIndex;
  const item = response.item;
  const id = response.response.data.id;
  const newTotal = response.response.data.total;

  const cols = list(item.map(thisItem => thisItem.value));

  // update total and push new item to the data store list, then sort by date
  const sortedRows = sortRowsByDate(
    reduction.getIn(['appState', 'pages', pageIndex, 'rows']).push(map({ id, cols })), pageIndex);
  const weeklyData = addWeeklyAverages(
    reduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  newReduction = newReduction
  .setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData)
  .setIn(['appState', 'pages', pageIndex, 'data', 'total'], newTotal)
  .setIn(
    ['appState', 'pages', pageIndex, 'data', 'numRows'],
    newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'numRows']) + 1
  );

  // recalculate overview data
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    const costItem = item.find(thisItem => thisItem.item === 'cost');
    const dateItem = item.find(thisItem => thisItem.item === 'date');
    if (typeof costItem === 'undefined' || typeof dateItem === 'undefined') {
      return rErrorMessageOpen(newReduction, map({
        level: ERROR_LEVEL_WARN,
        text: ERROR_MSG_BUG_INVALID_ITEM
      }));
    }
    newReduction = rCalculateOverview(
      newReduction, pageIndex, dateItem.value, dateItem.value, costItem.value, 0);
  }

  if (reduction.getIn(['appState', 'currentPageIndex']) !== pageIndex) {
    return newReduction;
  }

  // go back to the add form to add a new item
  const now = new YMD();
  return newReduction.setIn(['appState', 'edit', 'add'], getAddDefaultValues(pageIndex))
  .setIn(['appState', 'edit', 'active'], map({
    row: -1,
    col: 0,
    pageIndex,
    id: null,
    item: 'date',
    value: now,
    originalValue: now
  })).setIn(['appState', 'edit', 'addBtnFocus'], false);
};

export const rHandleSuggestions = (reduction, obj) => {
  const newReduction = reduction
  .setIn(['appState', 'edit', 'suggestions', 'loading'], false)
  .setIn(['appState', 'edit', 'suggestions', 'active'], -1);

  if (!obj || reduction.getIn(
    ['appState', 'edit', 'suggestions', 'reqId']
  ) !== obj.reqId) {
    // null object (clear), or changed input while suggestions were loading
    return newReduction
    .setIn(['appState', 'edit', 'suggestions', 'list'], list.of())
    .setIn(['appState', 'edit', 'suggestions', 'reqId'], null);
  }
  return newReduction.setIn(['appState', 'edit', 'suggestions', 'list'], obj.items);
};

export const rRequestSuggestions = (reduction, value) => {
  if (reduction.getIn(['appState', 'edit', 'suggestions', 'loading'])) {
    return reduction;
  }
  if (!value.length) {
    return rHandleSuggestions(reduction, null);
  }

  const apiKey = reduction.getIn(['appState', 'user', 'apiKey']);

  const active = reduction.getIn(['appState', 'edit', 'active']);
  const page = PAGES[active.get('pageIndex')];
  const column = active.get('item');

  const reqId = uuid(); // for keeping track of EditItem requests

  const req = { reqId, apiKey, page, column, value };
  return reduction.set('effects', reduction.get('effects').push(
    buildMessage(EF_SUGGESTIONS_REQUESTED, req)
  ))
  .setIn(['appState', 'edit', 'suggestions', 'loading'], true)
  .setIn(['appState', 'edit', 'suggestions', 'reqId'], reqId);
};

const rFundTransactions = (reduction, row, col, callback) => {
  const pageIndex = PAGES.indexOf('funds');
  const transactions = callback(reduction.getIn(
    row > -1
    ? ['appState', 'pages', pageIndex, 'rows', row, 'cols', col]
    : ['appState', 'edit', 'add', col]
  ));

  if (row > -1) {
    return reduction.setIn(
      ['appState', 'pages', pageIndex, 'rows', row, 'cols', col], transactions
    ).setIn(
      ['appState', 'edit', 'active', 'value'], transactions
    );
  }

  return reduction.setIn(
    ['appState', 'edit', 'add', col], transactions
  );
};

export const rChangeFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.setIn([item.key, item.column], item.value));
};

export const rAddFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.push(item));
};

export const rRemoveFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.remove(item.key));
};

<commit_msg>Reload block view when adding data<commit_after>/**
 * Carries out actions for the editable framework
 */

import { List as list, Map as map } from 'immutable';
import buildMessage from '../messageBuilder';
import { EF_SERVER_ADD_REQUESTED, EF_SUGGESTIONS_REQUESTED } from '../constants/effects';
import { rGetOverviewRows, rCalculateOverview, rProcessDataOverview } from './data/overview';
import { loadBlocks } from './data/list';
import { getGainComparisons, addPriceHistory } from './data/funds';
import {
  PAGES, LIST_PAGES, LIST_COLS_PAGES, ERROR_LEVEL_WARN, ERROR_LEVEL_ERROR
} from '../misc/const';
import {
  ERROR_MSG_BAD_DATA, ERROR_MSG_API_FAILED, ERROR_MSG_BUG_INVALID_ITEM
} from '../misc/config';
import { YMD } from '../misc/date';
import {
  uuid, getNullEditable, getAddDefaultValues, sortRowsByDate, addWeeklyAverages
} from '../misc/data';
import { rErrorMessageOpen } from './ErrorReducer';

const recalculateFundProfits = (reduction, pageIndex) => {
  const transactionsKey = LIST_COLS_PAGES[pageIndex].indexOf('transactions');
  const history = reduction.getIn(['appState', 'pages', pageIndex, 'history']);
  const oldRows = reduction.getIn(['appState', 'pages', pageIndex, 'rows']);
  const newRows = getGainComparisons(oldRows.map(row => {
    return addPriceHistory(pageIndex, row, history, row.getIn(['cols', transactionsKey]));
  }));
  return reduction.setIn(['appState', 'pages', pageIndex, 'rows'], newRows);
};

const overviewKey = PAGES.indexOf('overview');
const applyEditsOverview = (reduction, item) => {
  // update the balance for a row and recalculate overview data
  const value = item.get('value');
  const row = item.get('row');

  const newCost = reduction
  .getIn(['appState', 'pages', overviewKey, 'data', 'cost'])
  .setIn(['balance', row], value);

  const startYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'startYearMonth']);
  const endYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'endYearMonth']);
  const currentYearMonth = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'currentYearMonth']);
  const futureMonths = reduction.getIn(['appState', 'pages', overviewKey, 'data', 'futureMonths']);

  const newData = rProcessDataOverview(
    newCost, startYearMonth, endYearMonth, currentYearMonth, futureMonths);

  return reduction.setIn(['appState', 'pages', overviewKey, 'data'], newData)
  .setIn(['appState', 'pages', overviewKey, 'rows'], rGetOverviewRows(newData));
};

const applyEditsList = (reduction, item, pageIndex) => {
  // update list data in the UI
  if (item.get('row') === -1) {
    // add-item
    return reduction.setIn(['appState', 'edit', 'add', item.get('col')], item.get('value'));
  }

  // update row
  const newRow = reduction.getIn(
    ['appState', 'pages', pageIndex, 'rows', item.get('row')])
    .setIn(['cols', item.get('col')], item.get('value'));

  let newReduction = reduction.setIn(
    ['appState', 'pages', pageIndex, 'rows', item.get('row')], newRow);

  // recalculate total if the cost has changed
  if (item.get('item') === 'cost') {
    newReduction = newReduction.setIn(
      ['appState', 'pages', pageIndex, 'data', 'total'],
      newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'total']) +
        item.get('value') - item.get('originalValue')
    );
  }

  // recalculate fund profits / losses if transactions have changed
  if (PAGES[pageIndex] === 'funds' && item.get('item') === 'transactions') {
    newReduction = recalculateFundProfits(newReduction, pageIndex);
  }

  // sort rows by date
  const sortedRows = sortRowsByDate(
    newReduction.getIn(['appState', 'pages', pageIndex, 'rows']), pageIndex);
  const weeklyData = addWeeklyAverages(
    newReduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  newReduction = newReduction.setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData);

  // recalculate overview data if the cost or date changed
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    if (item.get('item') === 'cost') {
      const dateKey = LIST_COLS_PAGES[pageIndex].indexOf('date');
      const date = newReduction.getIn(
        ['appState', 'pages', pageIndex, 'rows', item.get('row'), 'cols', dateKey]);

      newReduction = rCalculateOverview(
        newReduction, pageIndex, date, date, item.get('value'), item.get('originalValue'));
    }
    else if (item.get('item') === 'date') {
      const costKey = LIST_COLS_PAGES[pageIndex].indexOf('cost');
      const cost = newRow.getIn(['cols', costKey]);

      newReduction = rCalculateOverview(
        newReduction, pageIndex, item.get('value'), item.get('originalValue'), cost, cost);
    }
  }

  return newReduction;
};

/**
 * applyEdits: apply editItem edits to UI (API handled separately)
 * @param {Record} reduction: reduction to modify and return
 * @param {map} item: edit item
 * @param {integer} pageIndex: index of the page on which edits are being done
 * @returns {Record} modified reduction
 */
const applyEdits = (reduction, item, pageIndex) => {
  if (pageIndex === 0) {
    return applyEditsOverview(reduction, item);
  }
  if (LIST_PAGES.indexOf(pageIndex) > -1) {
    return applyEditsList(reduction, item, pageIndex);
  }
  return reduction;
};

export const rActivateEditable = (reduction, editable, cancel) => {
  const active = reduction.getIn(['appState', 'edit', 'active']);
  const queue = reduction.getIn(['appState', 'edit', 'queue']);
  const pageIndex = reduction.getIn(['appState', 'currentPageIndex']);
  let newReduction = reduction
  .setIn(['appState', 'edit', 'addBtnFocus'], false)
  .setIn(['appState', 'edit', 'suggestions', 'list'], list.of())
  .setIn(['appState', 'edit', 'suggestions', 'active'], -1)
  .setIn(['appState', 'edit', 'suggestions', 'loading'], false)
  .setIn(['appState', 'edit', 'suggestions', 'reqId'], null);

  // confirm the previous item's edits
  if (active && active.get('value') !== active.get('originalValue')) {
    if (cancel) {
      // revert to previous state
      newReduction = applyEdits(newReduction, active.set('value', active.get('originalValue')));
    }
    else {
      if (active.get('row') > -1) {
        // add last item to queue for saving on API
        newReduction = newReduction.setIn(['appState', 'edit', 'queue'], queue.push(active));
      }

      // append the changes of the last item to the UI
      newReduction = applyEdits(newReduction, active, pageIndex);
    }
  }

  // can pass null to deactivate editing
  if (!editable) {
    return newReduction.setIn(['appState', 'edit', 'active'], getNullEditable(pageIndex));
  }

  return newReduction.setIn(
    ['appState', 'edit', 'active'],
    editable.set('originalValue', editable.get('value'))
  );
};

export const rChangeEditable = (reduction, value) => {
  return reduction.setIn(['appState', 'edit', 'active', 'value'], value);
};

export const rDeleteListItem = (reduction, item) => {
  let newReduction = reduction;

  const pageIndex = item.pageIndex;
  const id = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'id']);
  const dateKey = LIST_COLS_PAGES[pageIndex].indexOf('date');
  const costKey = LIST_COLS_PAGES[pageIndex].indexOf('cost');
  const itemCost = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'cols', costKey]);

  // recalculate total
  newReduction = newReduction.setIn(
    ['appState', 'pages', pageIndex, 'data', 'total'],
    newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'total']) - itemCost
  );
  // sort rows and recalculate weekly data
  const sortedRows = sortRowsByDate(
    newReduction.getIn(['appState', 'pages', pageIndex, 'rows']).splice(item.key, 1), pageIndex);
  const weeklyData = addWeeklyAverages(
    newReduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  // recalculate overview data
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    const date = reduction.getIn(['appState', 'pages', pageIndex, 'rows', item.key, 'cols', dateKey]);
    newReduction = rCalculateOverview(newReduction, pageIndex, date, date, 0, itemCost);
  }

  newReduction = newReduction.setIn(
    ['appState', 'edit', 'queueDelete'],
    reduction.getIn(['appState', 'edit', 'queueDelete']).push({ pageIndex, id })
  )
  .setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData);

  // recalculate fund profits / losses
  if (PAGES[pageIndex] === 'funds') {
    newReduction = recalculateFundProfits(newReduction, pageIndex);
  }

  return newReduction;
};

export const rAddListItem = (reduction, items) => {
  if (reduction.getIn(['appState', 'loadingApi'])) {
    return reduction;
  }

  // validate items
  const active = reduction.getIn(['appState', 'edit', 'active']);
  let activeItem = null;
  let activeValue = null;
  if (active && active.get('row') === -1) {
    activeItem = active.get('item');
    activeValue = active.get('value');
  }

  const theItems = items.map(column => {
    const item = column.props.item;
    const value = item === activeItem ? activeValue : column.props.value;

    return { item, value };
  });

  const valid = theItems.reduce((a, b) => {
    const thisValid = b.item === 'item' ? b.value.length > 0 : true; // others are self-validating
    return thisValid ? a : false;
  }, true);

  if (!valid) {
    return rErrorMessageOpen(reduction, map({
      level: ERROR_LEVEL_WARN,
      text: ERROR_MSG_BAD_DATA
    }));
  }

  const item = {};
  theItems.forEach(thisItem => {
    item[thisItem.item] = thisItem.value.toString();
  });

  const apiKey = reduction.getIn(['appState', 'user', 'apiKey']);
  const pageIndex = reduction.getIn(['appState', 'currentPageIndex']);
  const req = { apiKey, item, theItems, pageIndex };

  return rActivateEditable(reduction, null)
  .setIn(['appState', 'edit', 'add'], list.of())
  .setIn(['appState', 'loadingApi'], true)
  .set('effects', reduction.get('effects').push(buildMessage(EF_SERVER_ADD_REQUESTED, req)));
};

export const rHandleServerAdd = (reduction, response) => {
  // handle the response from adding an item to a list page
  let newReduction = reduction.setIn(['appState', 'loadingApi'], false);
  if (response.response.data.error) {
    return rErrorMessageOpen(newReduction, map({
      level: ERROR_LEVEL_ERROR,
      text: `${ERROR_MSG_API_FAILED}: ${response.response.data.errorText}`
    }));
  }
  const pageIndex = response.pageIndex;
  const item = response.item;
  const id = response.response.data.id;
  const newTotal = response.response.data.total;

  const cols = list(item.map(thisItem => thisItem.value));

  // update total and push new item to the data store list, then sort by date
  const sortedRows = sortRowsByDate(
    reduction.getIn(['appState', 'pages', pageIndex, 'rows']).push(map({ id, cols })), pageIndex);
  const weeklyData = addWeeklyAverages(
    reduction.getIn(['appState', 'pages', pageIndex, 'data']), sortedRows, pageIndex);

  newReduction = newReduction
  .setIn(['appState', 'pages', pageIndex, 'rows'], sortedRows)
  .setIn(['appState', 'pages', pageIndex, 'data'], weeklyData)
  .setIn(['appState', 'pages', pageIndex, 'data', 'total'], newTotal)
  .setIn(
    ['appState', 'pages', pageIndex, 'data', 'numRows'],
    newReduction.getIn(['appState', 'pages', pageIndex, 'data', 'numRows']) + 1
  );

  // recalculate overview data
  if (reduction.getIn(['appState', 'pagesLoaded', overviewKey])) {
    const costItem = item.find(thisItem => thisItem.item === 'cost');
    const dateItem = item.find(thisItem => thisItem.item === 'date');
    if (typeof costItem === 'undefined' || typeof dateItem === 'undefined') {
      return rErrorMessageOpen(newReduction, map({
        level: ERROR_LEVEL_WARN,
        text: ERROR_MSG_BUG_INVALID_ITEM
      }));
    }
    newReduction = rCalculateOverview(
      newReduction, pageIndex, dateItem.value, dateItem.value, costItem.value, 0);
  }

  // reload block view
  newReduction = loadBlocks(newReduction, pageIndex);

  if (reduction.getIn(['appState', 'currentPageIndex']) !== pageIndex) {
    return newReduction;
  }

  // go back to the add form to add a new item
  const now = new YMD();
  return newReduction.setIn(['appState', 'edit', 'add'], getAddDefaultValues(pageIndex))
  .setIn(['appState', 'edit', 'active'], map({
    row: -1,
    col: 0,
    pageIndex,
    id: null,
    item: 'date',
    value: now,
    originalValue: now
  })).setIn(['appState', 'edit', 'addBtnFocus'], false);
};

export const rHandleSuggestions = (reduction, obj) => {
  const newReduction = reduction
  .setIn(['appState', 'edit', 'suggestions', 'loading'], false)
  .setIn(['appState', 'edit', 'suggestions', 'active'], -1);

  if (!obj || reduction.getIn(
    ['appState', 'edit', 'suggestions', 'reqId']
  ) !== obj.reqId) {
    // null object (clear), or changed input while suggestions were loading
    return newReduction
    .setIn(['appState', 'edit', 'suggestions', 'list'], list.of())
    .setIn(['appState', 'edit', 'suggestions', 'reqId'], null);
  }
  return newReduction.setIn(['appState', 'edit', 'suggestions', 'list'], obj.items);
};

export const rRequestSuggestions = (reduction, value) => {
  if (reduction.getIn(['appState', 'edit', 'suggestions', 'loading'])) {
    return reduction;
  }
  if (!value.length) {
    return rHandleSuggestions(reduction, null);
  }

  const apiKey = reduction.getIn(['appState', 'user', 'apiKey']);

  const active = reduction.getIn(['appState', 'edit', 'active']);
  const page = PAGES[active.get('pageIndex')];
  const column = active.get('item');

  const reqId = uuid(); // for keeping track of EditItem requests

  const req = { reqId, apiKey, page, column, value };
  return reduction.set('effects', reduction.get('effects').push(
    buildMessage(EF_SUGGESTIONS_REQUESTED, req)
  ))
  .setIn(['appState', 'edit', 'suggestions', 'loading'], true)
  .setIn(['appState', 'edit', 'suggestions', 'reqId'], reqId);
};

const rFundTransactions = (reduction, row, col, callback) => {
  const pageIndex = PAGES.indexOf('funds');
  const transactions = callback(reduction.getIn(
    row > -1
    ? ['appState', 'pages', pageIndex, 'rows', row, 'cols', col]
    : ['appState', 'edit', 'add', col]
  ));

  if (row > -1) {
    return reduction.setIn(
      ['appState', 'pages', pageIndex, 'rows', row, 'cols', col], transactions
    ).setIn(
      ['appState', 'edit', 'active', 'value'], transactions
    );
  }

  return reduction.setIn(
    ['appState', 'edit', 'add', col], transactions
  );
};

export const rChangeFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.setIn([item.key, item.column], item.value));
};

export const rAddFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.push(item));
};

export const rRemoveFundTransactions = (reduction, item) => {
  return rFundTransactions(reduction,
    item.row, item.col, transactions => transactions.remove(item.key));
};

<|endoftext|>


<commit_before>import React from 'react';
import _ from 'lodash';
import ReactDOM from 'react-dom';
import {
	filterComponentsByExactName,
	filterComponentsInSectionsByExactName,
	processComponents,
	processSections,
} from './utils/utils';
import StyleGuide from 'rsg-components/StyleGuide';

import 'highlight.js/styles/tomorrow.css';
import './styles.css';

// Make libraries available in examples
global.React = React;
global._ = _;

let hasRenderedFullStyleguide = false;
let codeKey = 0;

function renderStyleguide() {
	const styleguide = require('styleguide!index.js');
	const config = {
		...styleguide.config,
		codeKey,
	};
	const components = processComponents(styleguide.components);
	const sections = processSections(styleguide.sections || []);

	const app = document.getElementById('app');

	if (window.location.hash.substr(0, 3) === '#!/') {
		const targetComponentName = window.location.hash.substr(3);

		const filteredComponents = [
			...filterComponentsByExactName(components, targetComponentName),
			...filterComponentsInSectionsByExactName(sections, targetComponentName),
		];

		ReactDOM.render(
			<StyleGuide
				config={config}
				components={filteredComponents}
				sections={[]}
				sidebar={false}
			/>,
			app
		);
		hasRenderedFullStyleguide = false;
	}
	else if (!hasRenderedFullStyleguide) {
		ReactDOM.render(
			<StyleGuide
				config={config}
				components={components}
				sections={sections}
			/>,
			app
		);
		hasRenderedFullStyleguide = true;
	}
}

window.addEventListener('hashchange', renderStyleguide);

if (module.hot) {
	module.hot.accept('styleguide!index.js', () => {
		codeKey += 1;
		hasRenderedFullStyleguide = false;
		renderStyleguide();
	});
}

renderStyleguide();
<commit_msg>Simplify styleguide main rendering function.<commit_after>import React from 'react';
import _ from 'lodash';
import ReactDOM from 'react-dom';
import {
	filterComponentsByExactName,
	filterComponentsInSectionsByExactName,
	processComponents,
	processSections,
} from './utils/utils';
import StyleGuide from 'rsg-components/StyleGuide';

import 'highlight.js/styles/tomorrow.css';
import './styles.css';

// Make libraries available in examples
global.React = React;
global._ = _;

let codeKey = 0;

function renderStyleguide() {
	const styleguide = require('styleguide!index.js');
	const config = {
		...styleguide.config,
		codeKey,
	};

	let components = processComponents(styleguide.components);
	let sections = processSections(styleguide.sections || []);
	let sidebar = true;

	if (window.location.hash.substr(0, 3) === '#!/') {
		const targetComponentName = window.location.hash.substr(3);
		components = [
			...filterComponentsByExactName(components, targetComponentName),
			...filterComponentsInSectionsByExactName(sections, targetComponentName),
		];
		sections = [];
		sidebar = false;
	}

	ReactDOM.render(
		<StyleGuide
			config={config}
			components={components}
			sections={sections}
			sidebar={sidebar}
		/>,
		document.getElementById('app')
	);
}

window.addEventListener('hashchange', renderStyleguide);

if (module.hot) {
	module.hot.accept('styleguide!index.js', () => {
		codeKey += 1;
		renderStyleguide();
	});
}

renderStyleguide();
<|endoftext|>


<commit_before>"use strict";

var path = require("path");
var extract = require("./extract");

var htmlExtensions = [
  ".hbs",
  ".handelbars",
  ".htm",
  ".html",
  ".mustache",
  ".php",
  ".twig",
  ".vue",
];

var xmlExtensions = [
  ".xhtml",
  ".xml",
];

// Disclaimer:
//
// This is not a long term viable solution. ESLint needs to improve its processor API to
// provide access to the configuration before actually preprocess files, but it's not
// planed yet. This solution is quite ugly but shouldn't alter eslint process.
//
// Related github issues:
// https://github.com/eslint/eslint/issues/3422
// https://github.com/eslint/eslint/issues/4153

var needle = path.join("eslint", "lib", "eslint.js");
var eslint;
for (var key in require.cache) {
  if (key.indexOf(needle, key.length - needle.length) >= 0) {
    eslint = require(key);
    break;
  }
}

if (!eslint) {
  throw new Error("eslint-plugin-html error: It seems that eslint is not loaded. " +
                  "If you think it is a bug, please file a report at " +
                  "https://github.com/BenoitZugmeyer/eslint-plugin-html/issues");
}

function createProcessor(defaultXMLMode) {
  var verify = eslint.verify;
  var reportBadIndent;

  var currentInfos;

  function patch() {
    eslint.verify = function (textOrSourceCode, config, filenameOrOptions, saveState) {
      var indentDescriptor = config.settings && config.settings["html/indent"];
      var xmlMode = config.settings && config.settings["html/xml-mode"];
      reportBadIndent = config.settings && config.settings["html/report-bad-indent"];

      if (typeof xmlMode !== "boolean") {
        xmlMode = defaultXMLMode;
      }

      currentInfos = extract(textOrSourceCode, {
        indent: indentDescriptor,
        reportBadIndent: Boolean(reportBadIndent),
        xmlMode: xmlMode,
      });
      return verify.call(this, currentInfos.code, config, filenameOrOptions, saveState);
    };
  }

  function unpatch() {
    eslint.verify = verify;
  }
  return {

    preprocess: function (content) {
      patch();
      return [content];
    },

    postprocess: function (messages) {
      unpatch();

      messages[0].forEach(function (message) {
        message.column += currentInfos.map[message.line] || 0;
      });

      currentInfos.badIndentationLines.forEach(function (line) {
        messages[0].push({
          message: "Bad line indentation.",
          line: line,
          column: 1,
          ruleId: "(html plugin)",
          severity: reportBadIndent === true ? 2 : reportBadIndent,
        });
      });

      messages[0].sort(function (ma, mb) {
        return ma.line - mb.line || ma.column - mb.column;
      });

      return messages[0];
    },

  };

}

var htmlProcessor = createProcessor(false);
var xmlProcessor = createProcessor(true);

var processors = {};

htmlExtensions.forEach(function(ext) {
  processors[ext] = htmlProcessor;
});

xmlExtensions.forEach(function(ext) {
  processors[ext] = xmlProcessor;
});

exports.processors = processors;
<commit_msg>Add .tag extension for riot tags.<commit_after>"use strict";

var path = require("path");
var extract = require("./extract");

var htmlExtensions = [
  ".hbs",
  ".handelbars",
  ".htm",
  ".html",
  ".mustache",
  ".php",
  ".tag",
  ".twig",
  ".vue",
];

var xmlExtensions = [
  ".xhtml",
  ".xml",
];

// Disclaimer:
//
// This is not a long term viable solution. ESLint needs to improve its processor API to
// provide access to the configuration before actually preprocess files, but it's not
// planed yet. This solution is quite ugly but shouldn't alter eslint process.
//
// Related github issues:
// https://github.com/eslint/eslint/issues/3422
// https://github.com/eslint/eslint/issues/4153

var needle = path.join("eslint", "lib", "eslint.js");
var eslint;
for (var key in require.cache) {
  if (key.indexOf(needle, key.length - needle.length) >= 0) {
    eslint = require(key);
    break;
  }
}

if (!eslint) {
  throw new Error("eslint-plugin-html error: It seems that eslint is not loaded. " +
                  "If you think it is a bug, please file a report at " +
                  "https://github.com/BenoitZugmeyer/eslint-plugin-html/issues");
}

function createProcessor(defaultXMLMode) {
  var verify = eslint.verify;
  var reportBadIndent;

  var currentInfos;

  function patch() {
    eslint.verify = function (textOrSourceCode, config, filenameOrOptions, saveState) {
      var indentDescriptor = config.settings && config.settings["html/indent"];
      var xmlMode = config.settings && config.settings["html/xml-mode"];
      reportBadIndent = config.settings && config.settings["html/report-bad-indent"];

      if (typeof xmlMode !== "boolean") {
        xmlMode = defaultXMLMode;
      }

      currentInfos = extract(textOrSourceCode, {
        indent: indentDescriptor,
        reportBadIndent: Boolean(reportBadIndent),
        xmlMode: xmlMode,
      });
      return verify.call(this, currentInfos.code, config, filenameOrOptions, saveState);
    };
  }

  function unpatch() {
    eslint.verify = verify;
  }
  return {

    preprocess: function (content) {
      patch();
      return [content];
    },

    postprocess: function (messages) {
      unpatch();

      messages[0].forEach(function (message) {
        message.column += currentInfos.map[message.line] || 0;
      });

      currentInfos.badIndentationLines.forEach(function (line) {
        messages[0].push({
          message: "Bad line indentation.",
          line: line,
          column: 1,
          ruleId: "(html plugin)",
          severity: reportBadIndent === true ? 2 : reportBadIndent,
        });
      });

      messages[0].sort(function (ma, mb) {
        return ma.line - mb.line || ma.column - mb.column;
      });

      return messages[0];
    },

  };

}

var htmlProcessor = createProcessor(false);
var xmlProcessor = createProcessor(true);

var processors = {};

htmlExtensions.forEach(function(ext) {
  processors[ext] = htmlProcessor;
});

xmlExtensions.forEach(function(ext) {
  processors[ext] = xmlProcessor;
});

exports.processors = processors;
<|endoftext|>


<commit_before>const Phaser = require('./Phaser')
const createRect = require('./createRect')
const Tile = require('./Tile')
const Animal = require('./Animal')

global.game = new Phaser.Game(window.innerWidth, window.innerHeight, Phaser.AUTO, '', {create, update})

game.MAP_SIZE = 50
game.TILE_SIZE = 20
game.ANIMAL_NUMBER = 20

const tiles = []
const animals = new Set()
let borders
let cursorKeys
let worldScale = 1
let player

function create () {
  cursorKeys = game.input.keyboard.createCursorKeys()
  game.physics.startSystem(Phaser.Physics.ARCADE)
  const size = game.MAP_SIZE * game.TILE_SIZE
  game.scale.scaleMode = Phaser.ScaleManager.RESIZE
  game.world.setBounds(-size, -size, size * 3, size * 3)
  game.stage.backgroundColor = '#555'
  game.animals = animals
  borders = createBorders()

  // Add an invisible player as a pivot for zoom.
  player = createRect(size / 2, size / 2, 1, 1, 'rgba(0,0,0,0)')
  game.camera.follow(player)

  for (let x = 0; x < game.MAP_SIZE; x++) {
    tiles[x] = []
    for (let y = 0; y < game.MAP_SIZE; y++) {
      tiles[x][y] = new Tile(x, y, game.TILE_SIZE, game)
    }
  }

  for (let i = 0; i < game.ANIMAL_NUMBER; i++) {
    spawnAnimal()
  }

  game.time.events.loop(16, tick)
}

function spawnAnimal () {
  const x = game.math.between(0, game.MAP_SIZE - 1)
  const y = game.math.between(0, game.MAP_SIZE - 1)
  animals.add(new Animal(x, y))
}

function update () {
  updateCamera()

  tiles.forEach(row => row.forEach(tile => tile.render()))
  animals.forEach(animal => {
    animal.render()
    game.physics.arcade.collide(animal.sprite, borders)
  })
}

function updateCamera () {
  if (cursorKeys.up.isDown) {
    player.y -= 25
  } else if (cursorKeys.down.isDown) {
    player.y += 25
  }

  if (cursorKeys.left.isDown) {
    player.x -= 25
  } else if (cursorKeys.right.isDown) {
    player.x += 25
  }

  if (game.input.keyboard.isDown(Phaser.Keyboard.Q)) worldScale += 0.1
  else if (game.input.keyboard.isDown(Phaser.Keyboard.A)) worldScale -= 0.1
  worldScale = game.math.clamp(worldScale, 0.5, 2)
  game.world.scale.set(worldScale)
}

function tick () {
  tiles.forEach(row => row.forEach(tile => tile.tick()))
  animals.forEach(animal => {
    animal.tick(tileAt(animal.getPosition()))
    if (animal.health <= 0) {
      animal.destroy()
      animals.delete(animal)
      spawnAnimal()
    }
  })
}

function tileAt (position) {
  return tiles[position.x][position.y]
}

function createBorders () {
  const size = game.MAP_SIZE * game.TILE_SIZE
  const bordersGroup = game.add.physicsGroup()

  const borders = [
    [0, -10, size, 10], // top
    [size, 0, 10, size], // right
    [0, size, size, 10], // bottom
    [-10, 0, 10, size] // left
  ]

  borders.forEach(dimensions => {
    const border = createRect(...dimensions, 'rgba(0,0,0,0)')
    game.physics.enable(border, Phaser.Physics.ARCADE)
    border.body.immovable = true
    bordersGroup.add(border)
  })

  return bordersGroup
}
<commit_msg>fix(index): don't spawn new animals on death if enough exist already<commit_after>const Phaser = require('./Phaser')
const createRect = require('./createRect')
const Tile = require('./Tile')
const Animal = require('./Animal')

global.game = new Phaser.Game(window.innerWidth, window.innerHeight, Phaser.AUTO, '', {create, update})

game.MAP_SIZE = 50
game.TILE_SIZE = 20
game.ANIMAL_NUMBER = 20

const tiles = []
const animals = new Set()
let borders
let cursorKeys
let worldScale = 1
let player

function create () {
  cursorKeys = game.input.keyboard.createCursorKeys()
  game.physics.startSystem(Phaser.Physics.ARCADE)
  const size = game.MAP_SIZE * game.TILE_SIZE
  game.scale.scaleMode = Phaser.ScaleManager.RESIZE
  game.world.setBounds(-size, -size, size * 3, size * 3)
  game.stage.backgroundColor = '#555'
  game.animals = animals
  borders = createBorders()

  // Add an invisible player as a pivot for zoom.
  player = createRect(size / 2, size / 2, 1, 1, 'rgba(0,0,0,0)')
  game.camera.follow(player)

  for (let x = 0; x < game.MAP_SIZE; x++) {
    tiles[x] = []
    for (let y = 0; y < game.MAP_SIZE; y++) {
      tiles[x][y] = new Tile(x, y, game.TILE_SIZE, game)
    }
  }

  for (let i = 0; i < game.ANIMAL_NUMBER; i++) {
    spawnAnimal()
  }

  game.time.events.loop(16, tick)
}

function spawnAnimal () {
  const x = game.math.between(0, game.MAP_SIZE - 1)
  const y = game.math.between(0, game.MAP_SIZE - 1)
  animals.add(new Animal(x, y))
}

function update () {
  updateCamera()

  tiles.forEach(row => row.forEach(tile => tile.render()))
  animals.forEach(animal => {
    animal.render()
    game.physics.arcade.collide(animal.sprite, borders)
  })
}

function updateCamera () {
  if (cursorKeys.up.isDown) {
    player.y -= 25
  } else if (cursorKeys.down.isDown) {
    player.y += 25
  }

  if (cursorKeys.left.isDown) {
    player.x -= 25
  } else if (cursorKeys.right.isDown) {
    player.x += 25
  }

  if (game.input.keyboard.isDown(Phaser.Keyboard.Q)) worldScale += 0.1
  else if (game.input.keyboard.isDown(Phaser.Keyboard.A)) worldScale -= 0.1
  worldScale = game.math.clamp(worldScale, 0.5, 2)
  game.world.scale.set(worldScale)
}

function tick () {
  tiles.forEach(row => row.forEach(tile => tile.tick()))
  animals.forEach(animal => {
    animal.tick(tileAt(animal.getPosition()))
    if (animal.health <= 0) {
      animal.destroy()
      animals.delete(animal)
      if (animals.size < game.ANIMAL_NUMBER) spawnAnimal()
    }
  })
}

function tileAt (position) {
  return tiles[position.x][position.y]
}

function createBorders () {
  const size = game.MAP_SIZE * game.TILE_SIZE
  const bordersGroup = game.add.physicsGroup()

  const borders = [
    [0, -10, size, 10], // top
    [size, 0, 10, size], // right
    [0, size, size, 10], // bottom
    [-10, 0, 10, size] // left
  ]

  borders.forEach(dimensions => {
    const border = createRect(...dimensions, 'rgba(0,0,0,0)')
    game.physics.enable(border, Phaser.Physics.ARCADE)
    border.body.immovable = true
    bordersGroup.add(border)
  })

  return bordersGroup
}
<|endoftext|>


<commit_before>import './styles/styles.scss';
import { slideToggle, slideUp, slideDown } from './libs/slide';
import {
  ANIMATION_DURATION,
  FIRST_SUB_MENUS_BTN,
  INNER_SUB_MENUS_BTN,
  SIDEBAR_EL,
} from './libs/constants';
import Poppers from './libs/poppers';

const PoppersInstance = new Poppers();

/**
 * wait for the current animation to finish and update poppers position
 */
const updatePoppersTimeout = () => {
  setTimeout(() => {
    PoppersInstance.updatePoppers();
  }, ANIMATION_DURATION);
};

/**
 * sidebar collapse handler
 */
document.getElementById('btn-collapse').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('collapsed');
  PoppersInstance.closePoppers();
  if (SIDEBAR_EL.classList.contains('collapsed'))
    FIRST_SUB_MENUS_BTN.forEach((element) => {
      element.parentElement.classList.remove('open');
    });

  updatePoppersTimeout();
});

/**
 * sidebar toggle handler (on break point )
 */
document.getElementById('btn-toggle').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('toggled');

  updatePoppersTimeout();
});

/**
 * toggle sidebar on overlay click
 */
document.getElementById('overlay').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('toggled');
});

const defaultOpenMenus = document.querySelectorAll('.menu-item.sub-menu.open');

defaultOpenMenus.forEach((element) => {
  element.lastElementChild.style.display = 'block';
});

/**
 * handle top level submenu click
 */
FIRST_SUB_MENUS_BTN.forEach((element) => {
  element.addEventListener('click', () => {
    if (SIDEBAR_EL.classList.contains('collapsed'))
      PoppersInstance.togglePopper(element.nextElementSibling);
    else {
      /**
       * if menu has "open-current-only" class then only one submenu opens at a time
       */
      const parentMenu = element.closest('.menu.open-current-only');
      if (parentMenu)
        parentMenu
          .querySelectorAll(':scope > ul > .menu-item.sub-menu > a')
          .forEach(
            (el) =>
              window.getComputedStyle(el.nextElementSibling).display !==
                'none' && slideUp(el.nextElementSibling)
          );
      slideToggle(element.nextElementSibling);
    }
  });
});

/**
 * handle inner submenu click
 */
INNER_SUB_MENUS_BTN.forEach((element) => {
  element.addEventListener('click', () => {
    slideToggle(element.nextElementSibling);
  });
});
<commit_msg>update class name<commit_after>import './styles/styles.scss';
import { slideToggle, slideUp, slideDown } from './libs/slide';
import {
  ANIMATION_DURATION,
  FIRST_SUB_MENUS_BTN,
  INNER_SUB_MENUS_BTN,
  SIDEBAR_EL,
} from './libs/constants';
import Poppers from './libs/poppers';

const PoppersInstance = new Poppers();

/**
 * wait for the current animation to finish and update poppers position
 */
const updatePoppersTimeout = () => {
  setTimeout(() => {
    PoppersInstance.updatePoppers();
  }, ANIMATION_DURATION);
};

/**
 * sidebar collapse handler
 */
document.getElementById('btn-collapse').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('collapsed');
  PoppersInstance.closePoppers();
  if (SIDEBAR_EL.classList.contains('collapsed'))
    FIRST_SUB_MENUS_BTN.forEach((element) => {
      element.parentElement.classList.remove('open');
    });

  updatePoppersTimeout();
});

/**
 * sidebar toggle handler (on break point )
 */
document.getElementById('btn-toggle').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('toggled');

  updatePoppersTimeout();
});

/**
 * toggle sidebar on overlay click
 */
document.getElementById('overlay').addEventListener('click', () => {
  SIDEBAR_EL.classList.toggle('toggled');
});

const defaultOpenMenus = document.querySelectorAll('.menu-item.sub-menu.open');

defaultOpenMenus.forEach((element) => {
  element.lastElementChild.style.display = 'block';
});

/**
 * handle top level submenu click
 */
FIRST_SUB_MENUS_BTN.forEach((element) => {
  element.addEventListener('click', () => {
    if (SIDEBAR_EL.classList.contains('collapsed'))
      PoppersInstance.togglePopper(element.nextElementSibling);
    else {
      /**
       * if menu has "open-current-only" class then only one submenu opens at a time
       */
      const parentMenu = element.closest('.menu.open-current-submenu');
      if (parentMenu)
        parentMenu
          .querySelectorAll(':scope > ul > .menu-item.sub-menu > a')
          .forEach(
            (el) =>
              window.getComputedStyle(el.nextElementSibling).display !==
                'none' && slideUp(el.nextElementSibling)
          );
      slideToggle(element.nextElementSibling);
    }
  });
});

/**
 * handle inner submenu click
 */
INNER_SUB_MENUS_BTN.forEach((element) => {
  element.addEventListener('click', () => {
    slideToggle(element.nextElementSibling);
  });
});
<|endoftext|>