<commit_before>(function() {
  var EDGE = -999;

  var root = this,   // Root object, this is going to be the window for now
      document = this.document, // Safely store a document here for us to use
      editableNodes = document.querySelectorAll(".g-body article"),
      editNode = editableNodes[0], // TODO: cross el support for imageUpload
      isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1,
      options = {
        animate: true
      },
      textMenu,
      optionsNode,
      urlInput,
      previouslySelectedText,
      imageTooltip,
      imageInput,
      imageBound;

      grande = {
        bind: function(bindableNodes, opts) {
          if (bindableNodes) {
            editableNodes = bindableNodes;
          }

          attachToolbarTemplate();
          bindTextSelectionEvents();
          bindTextStylingEvents();

          options = opts || options;
        },
        select: function() {
          triggerTextSelection();
        }
      },

      tagClassMap = {
        "b": "bold",
        "i": "italic",
        "h1": "header1",
        "h2": "header2",
        "a": "url",
        "blockquote": "quote"
      };

  function attachToolbarTemplate() {
    var div = document.createElement("div"),
        toolbarTemplate = "
 \
           \
             \
              <button class='bold'>B</button> \
              <button class='italic'>i</button> \
              <button class='header1'>h1</button> \
              <button class='header2'>h2</button> \
              <button class='quote'>”</button> \
              <button class='url useicons'></button> \
              <input class='url-input' type='text' placeholder='Paste or type a link'/> \
             \
           \

",
        imageTooltipTemplate = document.createElement("div");

    imageTooltipTemplate.innerHTML = "
Insert image
 \
                                        <input class='file-hidden pos-abs' type='file' id='files' name='files[]' accept='image/*' multiple/>";
    imageTooltipTemplate.className = "image-tooltip hide";

    div.className = "text-menu hide";
    div.innerHTML = toolbarTemplate;

    if (document.querySelectorAll(".text-menu").length === 0) {
      document.body.appendChild(div);
      document.body.appendChild(imageTooltipTemplate);
    }

    imageInput = document.querySelectorAll(".file-label + input")[0];
    imageTooltip = document.querySelectorAll(".image-tooltip")[0];
    textMenu = document.querySelectorAll(".text-menu")[0];
    optionsNode = document.querySelectorAll(".text-menu .options")[0];
    urlInput = document.querySelectorAll(".text-menu .url-input")[0];
  }

  function bindTextSelectionEvents() {
    var i,
        len,
        node;

    // Trigger on both mousedown and mouseup so that the click on the menu
    // feels more instantaneously active
    document.onmousedown = triggerTextSelection;
    document.onmouseup = function(event) {
      setTimeout(function() {
        triggerTextSelection(event);
      }, 1);
    };

    document.onkeydown = preprocessKeyDown;

    document.onkeyup = function(event){
      var sel = window.getSelection();

      // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
      if (sel.anchorNode && sel.anchorNode.nodeName !== "ARTICLE") {
        triggerNodeAnalysis(event);

        if (sel.isCollapsed) {
          triggerTextParse(event);
        }
      }
    };

    // Handle window resize events
    root.onresize = triggerTextSelection;

    urlInput.onblur = triggerUrlBlur;
    urlInput.onkeydown = triggerUrlSet;

    if (options.allowImages) {
      imageTooltip.onmousedown = triggerImageUpload;
      imageInput.onchange = uploadImage;
      document.onmousemove = triggerOverlayStyling;
    }

    for (i = 0, len = editableNodes.length; i < len; i++) {
      node = editableNodes[i];
      node.contentEditable = true;
      node.onmousedown = node.onkeyup = node.onmouseup = triggerTextSelection;
    }
  }

  function triggerOverlayStyling(event) {
    toggleImageTooltip(event, event.target);
  }

  function triggerImageUpload(event) {
    // Cache the bound that was originally clicked on before the image upload
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect();

    imageBound = getHorizontalBounds(childrenNodes, editBounds);
  }

  function uploadImage(event) {
    // Only allow uploading of 1 image for now, this is the first file
    var file = this.files[0],
        reader = new FileReader(),
        figEl;

    reader.onload = (function(f) {
      return function(e) {
        figEl = document.createElement("figure");
        figEl.innerHTML = "";
        editNode.insertBefore(figEl, imageBound.bottomElement);
      };
    }(file));

    reader.readAsDataURL(file);
  }

  function toggleImageTooltip(event, element) {
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect(),
        bound = getHorizontalBounds(childrenNodes, editBounds);

    if (bound) {
      imageTooltip.style.left = (editBounds.left - 90 ) + "px";
      imageTooltip.style.top = (bound.top - 17) + "px";
    } else {
      imageTooltip.style.left = EDGE + "px";
      imageTooltip.style.top = EDGE + "px";
    }
  }

  function getHorizontalBounds(nodes, target) {
    var bounds = [],
        bound,
        i,
        len,
        preNode,
        postNode,
        bottomBound,
        topBound,
        coordY;

    // Compute top and bottom bounds for each child element
    for (i = 0, len = nodes.length - 1; i < len; i++) {
      preNode = nodes[i];
      postNode = nodes[i+1] || null;

      bottomBound = preNode.getBoundingClientRect().bottom - 5;
      topBound = postNode.getBoundingClientRect().top;

      bounds.push({
        top: topBound,
        bottom: bottomBound,
        topElement: preNode,
        bottomElement: postNode,
        index: i+1
      });
    }

    coordY = event.pageY - root.scrollY;

    // Find if there is a range to insert the image tooltip between two elements
    for (i = 0, len = bounds.length; i < len; i++) {
      bound = bounds[i];
      if (coordY < bound.top && coordY > bound.bottom) {
        return bound;
      }
    }

    return null;
  }

  function iterateTextMenuButtons(callback) {
    var textMenuButtons = document.querySelectorAll(".text-menu button"),
        i,
        len,
        node;

    for (i = 0, len = textMenuButtons.length; i < len; i++) {
      node = textMenuButtons[i];

      (function(n) {
        callback(n);
      })(node);
    }
  }

  function bindTextStylingEvents() {
    iterateTextMenuButtons(function(node) {
      node.onmousedown = function(event) {
        triggerTextStyling(node);
      };
    });
  }

  function getFocusNode() {
    return root.getSelection().focusNode;
  }

  function reloadMenuState() {
    var className,
        focusNode = getFocusNode(),
        tagClass,
        reTag;

    iterateTextMenuButtons(function(node) {
      className = node.className;

      for (var tag in tagClassMap) {
        tagClass = tagClassMap[tag];
        reTag = new RegExp(tagClass);

        if (reTag.test(className)) {
          if (hasParentWithTag(focusNode, tag)) {
            node.className = tagClass + " active";
          } else {
            node.className = tagClass;
          }

          break;
        }
      }
    });
  }

  function preprocessKeyDown(event) {
    var sel = window.getSelection(),
        parentParagraph = getParentWithTag(sel.anchorNode, "p"),
        p,
        isHr;

    if (event.keyCode === 13 && parentParagraph) {
      prevSibling = parentParagraph.previousSibling;
      isHr = prevSibling && prevSibling.nodeName === "HR" &&
        !parentParagraph.textContent.length;

      // Stop enters from creating another
 after a
 on enter
      if (isHr) {
        event.preventDefault();
      }
    }
  }

  function triggerNodeAnalysis(event) {
    var sel = window.getSelection(),
        anchorNode,
        parentParagraph;

    if (event.keyCode === 13) {

      // Enters should replace it's parent
 with a

      if (sel.anchorNode.nodeName === "DIV") {
        toggleFormatBlock("p");
      }

      parentParagraph = getParentWithTag(sel.anchorNode, "p");

      if (parentParagraph) {
        insertHorizontalRule(parentParagraph);
      }
    }
  }

  function insertHorizontalRule(parentParagraph) {
    var prevSibling,
        prevPrevSibling,
        hr;

    prevSibling = parentParagraph.previousSibling;
    prevPrevSibling = prevSibling;

    while(prevPrevSibling = prevPrevSibling.previousSibling) {
      if (prevPrevSibling.nodeType != Node.TEXT_NODE) {
        break;
      }
    }

    if (prevSibling.nodeName === "P" && !prevSibling.textContent.length && prevPrevSibling.nodeName !== "HR") {
      hr = document.createElement("hr");
      hr.contentEditable = false;
      parentParagraph.parentNode.replaceChild(hr, prevSibling);
    }
  }

  function getTextProp(el) {
    var textProp;

    if (el.nodeType === Node.TEXT_NODE) {
      textProp = "data";
    } else if (isFirefox) {
      textProp = "textContent";
    } else {
      textProp = "innerText";
    }

    return textProp;
  }

  function insertListOnSelection(sel, textProp, listType) {
    var execListCommand = listType === "ol" ? "insertOrderedList" : "insertUnorderedList",
        nodeOffset = listType === "ol" ? 3 : 2;

    document.execCommand(execListCommand);
    sel.anchorNode[textProp] = sel.anchorNode[textProp].substring(nodeOffset);

    return getParentWithTag(sel.anchorNode, listType);
  }

  function triggerTextParse(event) {
    var sel = window.getSelection(),
        textProp,
        subject,
        insertedNode,
        unwrap,
        node,
        parent,
        range;

    // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
    if (!sel.isCollapsed || !sel.anchorNode || sel.anchorNode.nodeName === "ARTICLE") {
      return;
    }

    textProp = getTextProp(sel.anchorNode);
    subject = sel.anchorNode[textProp];

    if (subject.match(/^-\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ul");
    }

    if (subject.match(/^1\.\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ol");
    }

    unwrap = insertedNode &&
            ["ul", "ol"].indexOf(insertedNode.nodeName.toLocaleLowerCase()) >= 0 &&
            ["p", "div"].indexOf(insertedNode.parentNode.nodeName.toLocaleLowerCase()) >= 0;

    if (unwrap) {
      node = sel.anchorNode;
      parent = insertedNode.parentNode;
      parent.parentNode.insertBefore(insertedNode, parent);
      parent.parentNode.removeChild(parent);
      moveCursorToBeginningOfSelection(sel, node);
    }
  }

  function moveCursorToBeginningOfSelection(selection, node) {
    range = document.createRange();
    range.setStart(node, 0);
    range.setEnd(node, 0);
    selection.removeAllRanges();
    selection.addRange(range);
  }

  function triggerTextStyling(node) {
    var className = node.className,
        sel = window.getSelection(),
        selNode = sel.anchorNode,
        tagClass,
        reTag;

    for (var tag in tagClassMap) {
      tagClass = tagClassMap[tag];
      reTag = new RegExp(tagClass);

      if (reTag.test(className)) {
        switch(tag) {
          case "b":
            if (selNode && !hasParentWithTag(selNode, "h1") && !hasParentWithTag(selNode, "h2")) {
              document.execCommand(tagClass, false);
            }
            return;
          case "i":
            document.execCommand(tagClass, false);
            return;

          case "h1":
          case "h2":
          case "h3":
          case "blockquote":
            toggleFormatBlock(tag);
            return;

          case "a":
            toggleUrlInput();
            optionsNode.className = "options url-mode";
            return;
        }
      }
    }

    triggerTextSelection();
  }

  function triggerUrlBlur(event) {
    var url = urlInput.value;

    optionsNode.className = "options";
    window.getSelection().addRange(previouslySelectedText);

    document.execCommand("unlink", false);

    if (url === "") {
      return false;
    }

    if (!url.match("^(http://|https://|mailto:)")) {
      url = "http://" + url;
    }

    document.execCommand("createLink", false, url);

    urlInput.value = "";
  }

  function triggerUrlSet(event) {
    if (event.keyCode === 13) {
      event.preventDefault();
      event.stopPropagation();

      urlInput.blur();
    }
  }

  function toggleFormatBlock(tag) {
    if (hasParentWithTag(getFocusNode(), tag)) {
      document.execCommand("formatBlock", false, "p");
      document.execCommand("outdent");
    } else {
      document.execCommand("formatBlock", false, tag);
    }
  }

  function toggleUrlInput() {
    setTimeout(function() {
      var url = getParentHref(getFocusNode());

      if (typeof url !== "undefined") {
        urlInput.value = url;
      } else {
        document.execCommand("createLink", false, "/");
      }

      previouslySelectedText = window.getSelection().getRangeAt(0);

      urlInput.focus();
    }, 150);
  }

  function getParent(node, condition, returnCallback) {
    while (node.parentNode) {
      if (condition(node)) {
        return returnCallback(node);
      }

      node = node.parentNode;
    }
  }

  function getParentWithTag(node, nodeType) {
    var checkNodeType = function(node) { return node.nodeName.toLowerCase() === nodeType; },
        returnNode = function(node) { return node; };

    return getParent(node, checkNodeType, returnNode);
  }

  function hasParentWithTag(node, nodeType) {
    return !!getParentWithTag(node, nodeType);
  }

  function getParentHref(node) {
    var checkHref = function(node) { return typeof node.href !== "undefined"; },
        returnHref = function(node) { return node.href; };

    return getParent(node, checkHref, returnHref);
  }

  function triggerTextSelection() {
      var selectedText = root.getSelection(),
          range,
          clientRectBounds;

      // The selected text is collapsed, push the menu out of the way
      if (selectedText.isCollapsed) {
        setTextMenuPosition(EDGE, EDGE);
        textMenu.className = "text-menu hide";
      } else {
        range = selectedText.getRangeAt(0);
        clientRectBounds = range.getBoundingClientRect();

        // Every time we show the menu, reload the state
        reloadMenuState();
        setTextMenuPosition(
          clientRectBounds.top - 5 + root.pageYOffset,
          (clientRectBounds.left + clientRectBounds.right) / 2
        );
      }
  }

  function setTextMenuPosition(top, left) {
    textMenu.style.top = top + "px";
    textMenu.style.left = left + "px";

    if (options.animate) {
      if (top === EDGE) {
        textMenu.className = "text-menu hide";
      } else {
        textMenu.className = "text-menu active";
      }
    }
  }

  root.grande = grande;

}).call(this);
<commit_msg>Fix init order of options<commit_after>(function() {
  var EDGE = -999;

  var root = this,   // Root object, this is going to be the window for now
      document = this.document, // Safely store a document here for us to use
      editableNodes = document.querySelectorAll(".g-body article"),
      editNode = editableNodes[0], // TODO: cross el support for imageUpload
      isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1,
      options = {
        animate: true
      },
      textMenu,
      optionsNode,
      urlInput,
      previouslySelectedText,
      imageTooltip,
      imageInput,
      imageBound;

      grande = {
        bind: function(bindableNodes, opts) {
          if (bindableNodes) {
            editableNodes = bindableNodes;
          }

          options = opts || options;

          attachToolbarTemplate();
          bindTextSelectionEvents();
          bindTextStylingEvents();
        },
        select: function() {
          triggerTextSelection();
        }
      },

      tagClassMap = {
        "b": "bold",
        "i": "italic",
        "h1": "header1",
        "h2": "header2",
        "a": "url",
        "blockquote": "quote"
      };

  function attachToolbarTemplate() {
    var div = document.createElement("div"),
        toolbarTemplate = "
 \
           \
             \
              <button class='bold'>B</button> \
              <button class='italic'>i</button> \
              <button class='header1'>h1</button> \
              <button class='header2'>h2</button> \
              <button class='quote'>”</button> \
              <button class='url useicons'></button> \
              <input class='url-input' type='text' placeholder='Paste or type a link'/> \
             \
           \

",
        imageTooltipTemplate = document.createElement("div");

    imageTooltipTemplate.innerHTML = "
Insert image
 \
                                        <input class='file-hidden pos-abs' type='file' id='files' name='files[]' accept='image/*' multiple/>";
    imageTooltipTemplate.className = "image-tooltip hide";

    div.className = "text-menu hide";
    div.innerHTML = toolbarTemplate;

    if (document.querySelectorAll(".text-menu").length === 0) {
      document.body.appendChild(div);
      document.body.appendChild(imageTooltipTemplate);
    }

    imageInput = document.querySelectorAll(".file-label + input")[0];
    imageTooltip = document.querySelectorAll(".image-tooltip")[0];
    textMenu = document.querySelectorAll(".text-menu")[0];
    optionsNode = document.querySelectorAll(".text-menu .options")[0];
    urlInput = document.querySelectorAll(".text-menu .url-input")[0];
  }

  function bindTextSelectionEvents() {
    var i,
        len,
        node;

    // Trigger on both mousedown and mouseup so that the click on the menu
    // feels more instantaneously active
    document.onmousedown = triggerTextSelection;
    document.onmouseup = function(event) {
      setTimeout(function() {
        triggerTextSelection(event);
      }, 1);
    };

    document.onkeydown = preprocessKeyDown;

    document.onkeyup = function(event){
      var sel = window.getSelection();

      // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
      if (sel.anchorNode && sel.anchorNode.nodeName !== "ARTICLE") {
        triggerNodeAnalysis(event);

        if (sel.isCollapsed) {
          triggerTextParse(event);
        }
      }
    };

    // Handle window resize events
    root.onresize = triggerTextSelection;

    urlInput.onblur = triggerUrlBlur;
    urlInput.onkeydown = triggerUrlSet;

    if (options.allowImages) {
      imageTooltip.onmousedown = triggerImageUpload;
      imageInput.onchange = uploadImage;
      document.onmousemove = triggerOverlayStyling;
    }

    for (i = 0, len = editableNodes.length; i < len; i++) {
      node = editableNodes[i];
      node.contentEditable = true;
      node.onmousedown = node.onkeyup = node.onmouseup = triggerTextSelection;
    }
  }

  function triggerOverlayStyling(event) {
    toggleImageTooltip(event, event.target);
  }

  function triggerImageUpload(event) {
    // Cache the bound that was originally clicked on before the image upload
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect();

    imageBound = getHorizontalBounds(childrenNodes, editBounds);
  }

  function uploadImage(event) {
    // Only allow uploading of 1 image for now, this is the first file
    var file = this.files[0],
        reader = new FileReader(),
        figEl;

    reader.onload = (function(f) {
      return function(e) {
        figEl = document.createElement("figure");
        figEl.innerHTML = "";
        editNode.insertBefore(figEl, imageBound.bottomElement);
      };
    }(file));

    reader.readAsDataURL(file);
  }

  function toggleImageTooltip(event, element) {
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect(),
        bound = getHorizontalBounds(childrenNodes, editBounds);

    if (bound) {
      imageTooltip.style.left = (editBounds.left - 90 ) + "px";
      imageTooltip.style.top = (bound.top - 17) + "px";
    } else {
      imageTooltip.style.left = EDGE + "px";
      imageTooltip.style.top = EDGE + "px";
    }
  }

  function getHorizontalBounds(nodes, target) {
    var bounds = [],
        bound,
        i,
        len,
        preNode,
        postNode,
        bottomBound,
        topBound,
        coordY;

    // Compute top and bottom bounds for each child element
    for (i = 0, len = nodes.length - 1; i < len; i++) {
      preNode = nodes[i];
      postNode = nodes[i+1] || null;

      bottomBound = preNode.getBoundingClientRect().bottom - 5;
      topBound = postNode.getBoundingClientRect().top;

      bounds.push({
        top: topBound,
        bottom: bottomBound,
        topElement: preNode,
        bottomElement: postNode,
        index: i+1
      });
    }

    coordY = event.pageY - root.scrollY;

    // Find if there is a range to insert the image tooltip between two elements
    for (i = 0, len = bounds.length; i < len; i++) {
      bound = bounds[i];
      if (coordY < bound.top && coordY > bound.bottom) {
        return bound;
      }
    }

    return null;
  }

  function iterateTextMenuButtons(callback) {
    var textMenuButtons = document.querySelectorAll(".text-menu button"),
        i,
        len,
        node;

    for (i = 0, len = textMenuButtons.length; i < len; i++) {
      node = textMenuButtons[i];

      (function(n) {
        callback(n);
      })(node);
    }
  }

  function bindTextStylingEvents() {
    iterateTextMenuButtons(function(node) {
      node.onmousedown = function(event) {
        triggerTextStyling(node);
      };
    });
  }

  function getFocusNode() {
    return root.getSelection().focusNode;
  }

  function reloadMenuState() {
    var className,
        focusNode = getFocusNode(),
        tagClass,
        reTag;

    iterateTextMenuButtons(function(node) {
      className = node.className;

      for (var tag in tagClassMap) {
        tagClass = tagClassMap[tag];
        reTag = new RegExp(tagClass);

        if (reTag.test(className)) {
          if (hasParentWithTag(focusNode, tag)) {
            node.className = tagClass + " active";
          } else {
            node.className = tagClass;
          }

          break;
        }
      }
    });
  }

  function preprocessKeyDown(event) {
    var sel = window.getSelection(),
        parentParagraph = getParentWithTag(sel.anchorNode, "p"),
        p,
        isHr;

    if (event.keyCode === 13 && parentParagraph) {
      prevSibling = parentParagraph.previousSibling;
      isHr = prevSibling && prevSibling.nodeName === "HR" &&
        !parentParagraph.textContent.length;

      // Stop enters from creating another
 after a
 on enter
      if (isHr) {
        event.preventDefault();
      }
    }
  }

  function triggerNodeAnalysis(event) {
    var sel = window.getSelection(),
        anchorNode,
        parentParagraph;

    if (event.keyCode === 13) {

      // Enters should replace it's parent
 with a

      if (sel.anchorNode.nodeName === "DIV") {
        toggleFormatBlock("p");
      }

      parentParagraph = getParentWithTag(sel.anchorNode, "p");

      if (parentParagraph) {
        insertHorizontalRule(parentParagraph);
      }
    }
  }

  function insertHorizontalRule(parentParagraph) {
    var prevSibling,
        prevPrevSibling,
        hr;

    prevSibling = parentParagraph.previousSibling;
    prevPrevSibling = prevSibling;

    while(prevPrevSibling = prevPrevSibling.previousSibling) {
      if (prevPrevSibling.nodeType != Node.TEXT_NODE) {
        break;
      }
    }

    if (prevSibling.nodeName === "P" && !prevSibling.textContent.length && prevPrevSibling.nodeName !== "HR") {
      hr = document.createElement("hr");
      hr.contentEditable = false;
      parentParagraph.parentNode.replaceChild(hr, prevSibling);
    }
  }

  function getTextProp(el) {
    var textProp;

    if (el.nodeType === Node.TEXT_NODE) {
      textProp = "data";
    } else if (isFirefox) {
      textProp = "textContent";
    } else {
      textProp = "innerText";
    }

    return textProp;
  }

  function insertListOnSelection(sel, textProp, listType) {
    var execListCommand = listType === "ol" ? "insertOrderedList" : "insertUnorderedList",
        nodeOffset = listType === "ol" ? 3 : 2;

    document.execCommand(execListCommand);
    sel.anchorNode[textProp] = sel.anchorNode[textProp].substring(nodeOffset);

    return getParentWithTag(sel.anchorNode, listType);
  }

  function triggerTextParse(event) {
    var sel = window.getSelection(),
        textProp,
        subject,
        insertedNode,
        unwrap,
        node,
        parent,
        range;

    // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
    if (!sel.isCollapsed || !sel.anchorNode || sel.anchorNode.nodeName === "ARTICLE") {
      return;
    }

    textProp = getTextProp(sel.anchorNode);
    subject = sel.anchorNode[textProp];

    if (subject.match(/^-\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ul");
    }

    if (subject.match(/^1\.\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ol");
    }

    unwrap = insertedNode &&
            ["ul", "ol"].indexOf(insertedNode.nodeName.toLocaleLowerCase()) >= 0 &&
            ["p", "div"].indexOf(insertedNode.parentNode.nodeName.toLocaleLowerCase()) >= 0;

    if (unwrap) {
      node = sel.anchorNode;
      parent = insertedNode.parentNode;
      parent.parentNode.insertBefore(insertedNode, parent);
      parent.parentNode.removeChild(parent);
      moveCursorToBeginningOfSelection(sel, node);
    }
  }

  function moveCursorToBeginningOfSelection(selection, node) {
    range = document.createRange();
    range.setStart(node, 0);
    range.setEnd(node, 0);
    selection.removeAllRanges();
    selection.addRange(range);
  }

  function triggerTextStyling(node) {
    var className = node.className,
        sel = window.getSelection(),
        selNode = sel.anchorNode,
        tagClass,
        reTag;

    for (var tag in tagClassMap) {
      tagClass = tagClassMap[tag];
      reTag = new RegExp(tagClass);

      if (reTag.test(className)) {
        switch(tag) {
          case "b":
            if (selNode && !hasParentWithTag(selNode, "h1") && !hasParentWithTag(selNode, "h2")) {
              document.execCommand(tagClass, false);
            }
            return;
          case "i":
            document.execCommand(tagClass, false);
            return;

          case "h1":
          case "h2":
          case "h3":
          case "blockquote":
            toggleFormatBlock(tag);
            return;

          case "a":
            toggleUrlInput();
            optionsNode.className = "options url-mode";
            return;
        }
      }
    }

    triggerTextSelection();
  }

  function triggerUrlBlur(event) {
    var url = urlInput.value;

    optionsNode.className = "options";
    window.getSelection().addRange(previouslySelectedText);

    document.execCommand("unlink", false);

    if (url === "") {
      return false;
    }

    if (!url.match("^(http://|https://|mailto:)")) {
      url = "http://" + url;
    }

    document.execCommand("createLink", false, url);

    urlInput.value = "";
  }

  function triggerUrlSet(event) {
    if (event.keyCode === 13) {
      event.preventDefault();
      event.stopPropagation();

      urlInput.blur();
    }
  }

  function toggleFormatBlock(tag) {
    if (hasParentWithTag(getFocusNode(), tag)) {
      document.execCommand("formatBlock", false, "p");
      document.execCommand("outdent");
    } else {
      document.execCommand("formatBlock", false, tag);
    }
  }

  function toggleUrlInput() {
    setTimeout(function() {
      var url = getParentHref(getFocusNode());

      if (typeof url !== "undefined") {
        urlInput.value = url;
      } else {
        document.execCommand("createLink", false, "/");
      }

      previouslySelectedText = window.getSelection().getRangeAt(0);

      urlInput.focus();
    }, 150);
  }

  function getParent(node, condition, returnCallback) {
    while (node.parentNode) {
      if (condition(node)) {
        return returnCallback(node);
      }

      node = node.parentNode;
    }
  }

  function getParentWithTag(node, nodeType) {
    var checkNodeType = function(node) { return node.nodeName.toLowerCase() === nodeType; },
        returnNode = function(node) { return node; };

    return getParent(node, checkNodeType, returnNode);
  }

  function hasParentWithTag(node, nodeType) {
    return !!getParentWithTag(node, nodeType);
  }

  function getParentHref(node) {
    var checkHref = function(node) { return typeof node.href !== "undefined"; },
        returnHref = function(node) { return node.href; };

    return getParent(node, checkHref, returnHref);
  }

  function triggerTextSelection() {
      var selectedText = root.getSelection(),
          range,
          clientRectBounds;

      // The selected text is collapsed, push the menu out of the way
      if (selectedText.isCollapsed) {
        setTextMenuPosition(EDGE, EDGE);
        textMenu.className = "text-menu hide";
      } else {
        range = selectedText.getRangeAt(0);
        clientRectBounds = range.getBoundingClientRect();

        // Every time we show the menu, reload the state
        reloadMenuState();
        setTextMenuPosition(
          clientRectBounds.top - 5 + root.pageYOffset,
          (clientRectBounds.left + clientRectBounds.right) / 2
        );
      }
  }

  function setTextMenuPosition(top, left) {
    textMenu.style.top = top + "px";
    textMenu.style.left = left + "px";

    if (options.animate) {
      if (top === EDGE) {
        textMenu.className = "text-menu hide";
      } else {
        textMenu.className = "text-menu active";
      }
    }
  }

  root.grande = grande;

}).call(this);
<|endoftext|>


<commit_before>'use strict';

const log = require('../../lib/utils/utils');
const test = require('tape');
const group = require('tape-plus').group;
const util = require('util');

group('utils.log.error()', (test) => {
    test('logs errors', (t) => {
        t.equal(util.isFunction(log.log.error), true);
    });
});

group('utils.log.warn()', (test) => {
    test('logs warnings', (t) => {
        t.equal(util.isFunction(log.log.warn), true);
    });
});<commit_msg>Updated tests<commit_after>'use strict';

const log = require('../../lib/utils/utils');
const test = require('tape');
const group = require('tape-plus').group;
const util = require('util');

group('utils.log.error()', (test) => {
    test('logs errors', (t) => {
        log.log.error('Test');
        t.equal(util.isFunction(log.log.error), true);
    });
});

group('utils.log.warn()', (test) => {
    test('logs warnings', (t) => {
        log.log.warn('Test');
        t.equal(util.isFunction(log.log.warn), true);
    });
});<|endoftext|>


<commit_before>'use strict';

var test = require('tape');
var path = require('path');

var StateManager = require('./../lib');

test('StateManager', function(t) {
  t.ok(StateManager, 'exports');

  var dogState = {
    age: 4,
    breed: 'Yorkshire Terrier',
    isSleeping: false,
    isHungry: true
  }

  var globalObserverFlag = false;
  var ageObserverFlag = false;

  var globalObserver = function (key, value) {
    globalObserverFlag = true;
  }

  var SM = new StateManager(dogState, globalObserver);

  console.log('GETTER');
  t.equal(SM.get('age'), 4);
  t.equal(SM.get('breed'), 'Yorkshire Terrier');
  t.equal(SM.get('isSleeping'), false);
  t.equal(SM.get('isHungry'), true);

  SM.set('isSleeping', true);

  console.log('SETTER');
  t.equal(SM.get('isSleeping'), true);

  var ageObserver = function(key, value) {
    ageObserverFlag = true;
  }

  console.log('SUBSCRIBE');
  SM.subscribeTo('age', ageObserver);
  t.equal(SM._observers['age'][0], globalObserver);
  t.equal(SM._observers['age'][1], ageObserver);

  console.log('OBSERVER');
  SM.set('age', 5);
  t.equal(globalObserverFlag, true);
  t.equal(ageObserverFlag, true);
  t.equal(SM._observerExists('age', ageObserver), true);
  t.equal(SM._observerExists('breed', ageObserver), false);

  t.end();
});
<commit_msg>tests: add tests for operations<commit_after>'use strict';

var test = require('tape');
var path = require('path');

var StateManager = require('./../lib');

test('StateManager', function(t) {
  t.ok(StateManager, 'exports');

  var dogState = {
    age: 4,
    name: 'Yorkie',
    breed: 'Yorkshire Terrier',
    isSleeping: false,
    isHungry: true,
    cutenessLevel: 8.1,
    playfulnessLevel: 8.8
  }

  var globalObserverFlag = false;
  var ageObserverFlag = false;

  var globalObserver = function (key, value) {
    globalObserverFlag = true;
  }

  var SM = new StateManager(dogState, globalObserver);

  console.log('GETTER');
  t.equal(SM.get('age'), 4);
  t.equal(SM.get('breed'), 'Yorkshire Terrier');
  t.equal(SM.get('isSleeping'), false);
  t.equal(SM.get('isHungry'), true);

  SM.set('isSleeping', true);

  console.log('SETTER');
  t.equal(SM.get('isSleeping'), true);

  var ageObserver = function(key, value) {
    ageObserverFlag = true;
  }

  console.log('SUBSCRIBE');
  SM.subscribeTo('age', ageObserver);
  t.equal(SM._observers['age'][0], globalObserver);
  t.equal(SM._observers['age'][1], ageObserver);

  console.log('OBSERVER');
  SM.set('age', 5);
  t.equal(globalObserverFlag, true);
  t.equal(ageObserverFlag, true);
  t.equal(SM._observerExists('age', ageObserver), true);
  t.equal(SM._observerExists('breed', ageObserver), false);

  console.log('OPERATION');
  SM.add('age', 1);
  t.equal(SM.get('age'), 6);
  SM.subtract('age', 1);
  t.equal(SM.get('age'), 5);
  SM.multiply('age', 2);
  t.equal(SM.get('age'), 10);
  SM.timesPI('age');
  t.equal(SM.get('age'), 10 * Math.PI);
  SM.divide('age', 2 * Math.PI);
  t.equal(SM.get('age'), 5);
  SM.pow('age', 2);
  t.equal(SM.get('age'), 25);
  SM.sqrt('age');
  t.equal(SM.get('age'), 5);
  SM.multiply('age', -1);
  SM.abs('age');
  t.equal(SM.get('age'), 5);
  SM.sin('age');
  t.equal(SM.get('age'), Math.sin(5));
  SM.cos('age');
  t.equal(SM.get('age'), Math.cos(Math.sin(5)));
  SM.tan('age');
  t.equal(SM.get('age'), Math.tan(Math.cos(Math.sin(5))));
  SM.ceil('playfulnessLevel');
  t.equal(SM.get('playfulnessLevel'), 8);
  SM.ceil('cutenessLevel');
  t.equal(SM.get('cutenessLevel'), 8);
  SM.concat('name', 'Yorkie');
  t.equal(SM.get('name'), 'YorkieYorkie');
  SM.substring('name', [0, 6]);
  t.equal(SM.get('name'), 'Yorkie');
  SM.toUpper('name');
  t.equal(SM.get('name'), 'YORKIE');
  SM.toLower('name');
  t.equal(SM.get('name'), 'yorkie');
  SM.flip('isHungry');
  t.equal(SM.get('isHungry'), false);
  SM.toInt('isHungry');
  SM.addOperator('triple', function (a) { return 3 * a });
  SM.operate('cutenessLevel', null, 'triple');
  t.equal(SM.get('cutenessLevel'), 24);

  t.end();
});
<|endoftext|>


<commit_before>var requireEnv = require('../'),
	assert = require('assert'),
	expect = require('chai').expect;

require('dotenv').config({path: './tests/.env'});

describe('require-environment-variables', function () {

	it('should verify that process.env.THISISATEST exists', function (done) {
		expect(function () {
			requireEnv(['THISISATEST'])
		}).to.not.throw('Environment variables missing');
		done();
	});

	it('should throw an error that process.env.THISDOESNTEXIST is missing', function (done) {
		expect(function () {
			requireEnv(['THISDOESNTEXIST'])
		}).to.throw('Environment variables missing');
		done();
	});

});<commit_msg>editing test to reflect new change<commit_after>var requireEnv = require('../'),
	assert = require('assert'),
	expect = require('chai').expect;

require('dotenv').config({path: './tests/.env'});

describe('require-environment-variables', function () {

	it('should verify that process.env.THISISATEST exists', function (done) {
		expect(function () {
			requireEnv(['THISISATEST']);
		}).to.not.throw('Environment variables missing');
		done();
	});

	it('should throw an error that process.env.THISDOESNTEXIST is missing', function (done) {
		expect(function () {
			requireEnv(['THISDOESNTEXIST']);
		}).to.throw(Error, /Environment variables missing/);
		done();
	});

});
<|endoftext|>


<commit_before>import chai from 'chai'
var assert = chai.assert

import * as individual from '../lib/metis/individual.js'

describe('Individual object', () => {
    it('Basic creation', () => {
        let ind = new individual.Individual()
        assert.isDefined(ind.id)
        assert.equal(ind.alive, true)
    })
    it('generate_basic_individual', () => {
        let ind = new individual.generate_basic_individual()
        assert.isDefined(ind.id)
    })
    it('assign_random_sex', () => {
        let ind = new individual.generate_basic_individual()
        individual.assign_random_sex(ind)
        assert.isDefined(ind.is_female)
    })
})
<commit_msg>test species and cycle born<commit_after>import chai from 'chai'
var assert = chai.assert

import * as individual from '../lib/metis/individual.js'
import * as utils from './utils.js'

describe('Individual object', () => {
    it('Basic creation', () => {
        let ind = new individual.Individual()
        assert.isDefined(ind.id)
        assert.equal(ind.alive, true)
    })
    it('generate_basic_individual', () => {
        let ind = new individual.generate_basic_individual(utils.empty_species, 5)
        assert.equal(ind.cycle_born, 5)
        assert.isDefined(ind.id)
    })
    it('assign_random_sex', () => {
        let ind = new individual.generate_basic_individual()
        individual.assign_random_sex(ind)
        assert.isDefined(ind.is_female)
    })
})
<|endoftext|>


<commit_before>"use strict";

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

const Exchange = require ('./base/Exchange')
const { ExchangeError, InvalidOrder, InsufficientFunds, OrderNotFound } = require ('./base/errors')

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

module.exports = class kucoin extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'kucoin',
            'name': 'Kucoin',
            'countries': 'HK', // Hong Kong
            'version': 'v1',
            'rateLimit': 1500,
            'hasCORS': false,
            // obsolete metainfo interface
            'hasFetchTickers': true,
            'hasFetchOHLCV': false, // see the method implementation below
            'hasFetchOrder': true,
            'hasFetchOrders': true,
            'hasFetchClosedOrders': true,
            'hasFetchOpenOrders': true,
            'hasFetchMyTrades': false,
            'hasFetchCurrencies': true,
            'hasWithdraw': true,
            // new metainfo interface
            'has': {
                'fetchTickers': true,
                'fetchOHLCV': true, // see the method implementation below
                'fetchOrder': true,
                'fetchOrders': true,
                'fetchClosedOrders': 'emulated',
                'fetchOpenOrders': true,
                'fetchMyTrades': false,
                'fetchCurrencies': true,
                'withdraw': true,
            },
            'timeframes': {
                '1m': '1min',
                '5m': '5min',
                '15m': '15min',
                '30m': '30min',
                '1h': '1hour',
                '8h': '8hour',
                '1d': '1day',
                '1w': '1week',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/33795655-b3c46e48-dcf6-11e7-8abe-dc4588ba7901.jpg',
                'api': 'https://api.kucoin.com',
                'www': 'https://kucoin.com',
                'doc': 'https://kucoinapidocs.docs.apiary.io',
                'fees': 'https://news.kucoin.com/en/fee',
            },
            'api': {
                'public': {
                    'get': [
                        'open/chart/config',
                        'open/chart/history',
                        'open/chart/symbol',
                        'open/currencies',
                        'open/deal-orders',
                        'open/kline',
                        'open/lang-list',
                        'open/orders',
                        'open/orders-buy',
                        'open/orders-sell',
                        'open/tick',
                        'market/open/coin-info',
                        'market/open/coins',
                        'market/open/coins-trending',
                        'market/open/symbols',
                    ],
                },
                'private': {
                    'get': [
                        'account/balance',
                        'account/{coin}/wallet/address',
                        'account/{coin}/wallet/records',
                        'account/{coin}/balance',
                        'account/promotion/info',
                        'account/promotion/sum',
                        'deal-orders',
                        'order/active',
                        'order/dealt',
                        'referrer/descendant/count',
                        'user/info',
                    ],
                    'post': [
                        'account/{coin}/withdraw/apply',
                        'account/{coin}/withdraw/cancel',
                        'cancel-order',
                        'order',
                        'user/change-lang',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.0010,
                    'taker': 0.0010,
                },
            },
        });
    }

    async fetchMarkets () {
        let response = await this.publicGetMarketOpenSymbols ();
        let markets = response['data'];
        let result = [];
        for (let i = 0; i < markets.length; i++) {
            let market = markets[i];
            let id = market['symbol'];
            let base = market['coinType'];
            let quote = market['coinTypePair'];
            base = this.commonCurrencyCode (base);
            quote = this.commonCurrencyCode (quote);
            let symbol = base + '/' + quote;
            let precision = {
                'amount': 8,
                'price': 8,
            };
            let active = market['trading'];
            result.push (this.extend (this.fees['trading'], {
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'active': active,
                'info': market,
                'lot': Math.pow (10, -precision['amount']),
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision['amount']),
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            }));
        }
        return result;
    }

    async fetchCurrencies (params = {}) {
        let response = await this.publicGetMarketOpenCoins (params);
        let currencies = response['data'];
        let result = {};
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let id = currency['coin'];
            // todo: will need to rethink the fees
            // to add support for multiple withdrawal/deposit methods and
            // differentiated fees for each particular method
            let code = this.commonCurrencyCode (id);
            let precision = {
                'amount': currency['tradePrecision'],
                'price': currency['tradePrecision'],
            };
            let deposit = currency['enableDeposit'];
            let withdraw = currency['enableWithdraw'];
            let active = (deposit && withdraw);
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': currency['name'],
                'active': active,
                'status': 'ok',
                'fee': currency['withdrawFeeRate'], // todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision['amount']),
                        'max': Math.pow (10, precision['amount']),
                    },
                    'price': {
                        'min': Math.pow (10, -precision['price']),
                        'max': Math.pow (10, precision['price']),
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': currency['withdrawMinAmount'],
                        'max': Math.pow (10, precision['amount']),
                    },
                },
            };
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        throw new ExchangeError (this.id + ' fetchBalance() / private API not implemented yet');
        //  JUNK FROM SOME OTHER EXCHANGE, TEMPLATE
        //  let response = await this.accountGetBalances ();
        //  let balances = response['result'];
        //  let result = { 'info': balances };
        //  let indexed = this.indexBy (balances, 'Currency');
        //  let keys = Object.keys (indexed);
        //  for (let i = 0; i < keys.length; i++) {
        //      let id = keys[i];
        //      let currency = this.commonCurrencyCode (id);
        //      let account = this.account ();
        //      let balance = indexed[id];
        //      let free = parseFloat (balance['Available']);
        //      let total = parseFloat (balance['Balance']);
        //      let used = total - free;
        //      account['free'] = free;
        //      account['used'] = used;
        //      account['total'] = total;
        //      result[currency] = account;
        //  }
        // return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenOrders (this.extend ({
            'symbol': market['id'],
        }, params));
        let orderbook = response['data'];
        return this.parseOrderBook (orderbook, undefined, 'BUY', 'SELL');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = ticker['datetime'];
        let symbol = undefined;
        if (market) {
            symbol = market['symbol'];
        } else {
            symbol = ticker['coinType'] + '/' + ticker['coinTypePair'];
        }
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'buy'),
            'ask': this.safeFloat (ticker, 'sell'),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': this.safeFloat (ticker, 'lastDealPrice'),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'vol'),
            'quoteVolume': this.safeFloat (ticker, 'volValue'),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        let response = await this.publicGetMarketOpenSymbols (params);
        let tickers = response['data'];
        let result = {};
        for (let t = 0; t < tickers.length; t++) {
            let ticker = this.parseTicker (tickers[t]);
            let symbol = ticker['symbol'];
            result[symbol] = ticker;
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenTick (this.extend ({
            'symbol': market['id'],
        }, params));
        let ticker = response['data'];
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = trade[0];
        let side = undefined;
        if (trade[1] == 'BUY') {
            side = 'buy';
        } else if (trade[1] == 'SELL') {
            side = 'sell';
        }
        return {
            'id': undefined,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': 'limit',
            'side': side,
            'price': trade[2],
            'amount': trade[3],
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenDealOrders (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    parseOHLCV (ohlcv, market = undefined, timeframe = '1d', since = undefined, limit = undefined) {
        let timestamp = this.parse8601 (ohlcv['T']);
        return [
            timestamp,
            ohlcv['O'],
            ohlcv['H'],
            ohlcv['L'],
            ohlcv['C'],
            ohlcv['V'],
        ];
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let to = this.seconds ();
        // whatever I try with from + to + limit it does not work (always an empty response)
        // tried all combinations:
        // - reversing them
        // - changing directions
        // - seconds
        // - milliseconds
        // - datetime strings
        // the endpoint doesn't seem to work, or something is missing in their docs
        let request = {
            'symbol': market['id'],
            'type': this.timeframes[timeframe],
            'from': to - 86400,
            'to': to,
        };
        if (since) {
            request['from'] = parseInt (since / 1000);
        }
        if (limit) {
            request['limit'] = limit;
        }
        let response = await this.publicGetOpenKline (this.extend (request, params));
        return this.parseOHLCVs (response['data'], market, timeframe, since, limit);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let endpoint = '/' + this.version + '/' + this.implodeParams (path, params);
        let url = this.urls['api'] + endpoint;
        let query = this.omit (params, this.extractParams (path));
        if (api == 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            throw new ExchangeError (this.id + ' private API not implemented yet');
            // ---------------------------------
            // FROM KUCOIN:
            // String host = "https://api.kucoin.com";
            // String endpoint = "/v1/KCS-BTC/order"; // API endpoint
            // String secret; // The secret assigned when the API created
            // POST parameters：
            //     type: BUY
            //     amount: 10
            //     price: 1.1
            //     Arrange the parameters in ascending alphabetical order (lower cases first), then combine them with & (don't urlencode them, don't add ?, don't add extra &), e.g. amount=10&price=1.1&type=BUY
            //     将查询参数按照字母升序(小字母在前)排列后用&进行连接(请不要进行urlencode操作,开头不要带?,首位不要有额外的&符号)得到的queryString如:  amount=10&price=1.1&type=BUY
            // String queryString;
            // // splice string for signing
            // String strForSign = endpoint + "/" + nonce + "/" + queryString;
            // // Make a Base64 encoding of the completed string
            // String signatureStr = Base64.getEncoder().encodeToString(strForSign.getBytes("UTF-8"));
            // // KC-API-SIGNATURE in header
            // String signatureResult = hmacEncrypt("HmacSHA256", signatureStr, secret);
            // ----------------------------------
            // TEMPLATE (it is close, but it still needs testing and debugging):
            this.checkRequiredCredentials ();
            // their nonce is always a calibrated synched milliseconds-timestamp
            let nonce = this.milliseconds ();
            let queryString = '';
            nonce = nonce.toString ();
            if (Object.keys (query).length) {
                queryString = this.rawencode (this.keysort (query));
                if (method == 'GET') {
                    url += '?' + queryString;
                } else {
                    body = queryString;
                }
            }
            let auth = endpoint + '/' + nonce + '/' + queryString;
            let payload = this.stringToBase64 (this.encode (auth));
            // payload should be "encoded" as returned from stringToBase64
            let signature = this.hmac (payload, this.encode (this.secret), 'sha512');
            headers = {
                'KC-API-KEY': this.apiKey (),
                'KC-API-NONCE': nonce,
                'KC-API-SIGNATURE': signature,
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body) {
        if (code >= 400) {
            if (body[0] == "{") {
                let response = JSON.parse (body);
                if ('success' in response) {
                    if (!response['success']) {
                        if ('message' in response) {
                            if (response['message'] == 'MIN_TRADE_REQUIREMENT_NOT_MET')
                                throw new InvalidOrder (this.id + ' ' + this.json (response));
                            if (response['message'] == 'APIKEY_INVALID')
                                throw new AuthenticationError (this.id + ' ' + this.json (response));
                        }
                        throw new ExchangeError (this.id + ' ' + this.json (response));
                    }
                }
            }
        }
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        return response;
    }
}
<commit_msg>kucoin rateLimit = 2000<commit_after>"use strict";

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

const Exchange = require ('./base/Exchange')
const { ExchangeError, InvalidOrder, InsufficientFunds, OrderNotFound } = require ('./base/errors')

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

module.exports = class kucoin extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'kucoin',
            'name': 'Kucoin',
            'countries': 'HK', // Hong Kong
            'version': 'v1',
            'rateLimit': 2000,
            'hasCORS': false,
            // obsolete metainfo interface
            'hasFetchTickers': true,
            'hasFetchOHLCV': false, // see the method implementation below
            'hasFetchOrder': true,
            'hasFetchOrders': true,
            'hasFetchClosedOrders': true,
            'hasFetchOpenOrders': true,
            'hasFetchMyTrades': false,
            'hasFetchCurrencies': true,
            'hasWithdraw': true,
            // new metainfo interface
            'has': {
                'fetchTickers': true,
                'fetchOHLCV': true, // see the method implementation below
                'fetchOrder': true,
                'fetchOrders': true,
                'fetchClosedOrders': 'emulated',
                'fetchOpenOrders': true,
                'fetchMyTrades': false,
                'fetchCurrencies': true,
                'withdraw': true,
            },
            'timeframes': {
                '1m': '1min',
                '5m': '5min',
                '15m': '15min',
                '30m': '30min',
                '1h': '1hour',
                '8h': '8hour',
                '1d': '1day',
                '1w': '1week',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/33795655-b3c46e48-dcf6-11e7-8abe-dc4588ba7901.jpg',
                'api': 'https://api.kucoin.com',
                'www': 'https://kucoin.com',
                'doc': 'https://kucoinapidocs.docs.apiary.io',
                'fees': 'https://news.kucoin.com/en/fee',
            },
            'api': {
                'public': {
                    'get': [
                        'open/chart/config',
                        'open/chart/history',
                        'open/chart/symbol',
                        'open/currencies',
                        'open/deal-orders',
                        'open/kline',
                        'open/lang-list',
                        'open/orders',
                        'open/orders-buy',
                        'open/orders-sell',
                        'open/tick',
                        'market/open/coin-info',
                        'market/open/coins',
                        'market/open/coins-trending',
                        'market/open/symbols',
                    ],
                },
                'private': {
                    'get': [
                        'account/balance',
                        'account/{coin}/wallet/address',
                        'account/{coin}/wallet/records',
                        'account/{coin}/balance',
                        'account/promotion/info',
                        'account/promotion/sum',
                        'deal-orders',
                        'order/active',
                        'order/dealt',
                        'referrer/descendant/count',
                        'user/info',
                    ],
                    'post': [
                        'account/{coin}/withdraw/apply',
                        'account/{coin}/withdraw/cancel',
                        'cancel-order',
                        'order',
                        'user/change-lang',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.0010,
                    'taker': 0.0010,
                },
            },
        });
    }

    async fetchMarkets () {
        let response = await this.publicGetMarketOpenSymbols ();
        let markets = response['data'];
        let result = [];
        for (let i = 0; i < markets.length; i++) {
            let market = markets[i];
            let id = market['symbol'];
            let base = market['coinType'];
            let quote = market['coinTypePair'];
            base = this.commonCurrencyCode (base);
            quote = this.commonCurrencyCode (quote);
            let symbol = base + '/' + quote;
            let precision = {
                'amount': 8,
                'price': 8,
            };
            let active = market['trading'];
            result.push (this.extend (this.fees['trading'], {
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'active': active,
                'info': market,
                'lot': Math.pow (10, -precision['amount']),
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision['amount']),
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            }));
        }
        return result;
    }

    async fetchCurrencies (params = {}) {
        let response = await this.publicGetMarketOpenCoins (params);
        let currencies = response['data'];
        let result = {};
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let id = currency['coin'];
            // todo: will need to rethink the fees
            // to add support for multiple withdrawal/deposit methods and
            // differentiated fees for each particular method
            let code = this.commonCurrencyCode (id);
            let precision = {
                'amount': currency['tradePrecision'],
                'price': currency['tradePrecision'],
            };
            let deposit = currency['enableDeposit'];
            let withdraw = currency['enableWithdraw'];
            let active = (deposit && withdraw);
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': currency['name'],
                'active': active,
                'status': 'ok',
                'fee': currency['withdrawFeeRate'], // todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision['amount']),
                        'max': Math.pow (10, precision['amount']),
                    },
                    'price': {
                        'min': Math.pow (10, -precision['price']),
                        'max': Math.pow (10, precision['price']),
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': currency['withdrawMinAmount'],
                        'max': Math.pow (10, precision['amount']),
                    },
                },
            };
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        throw new ExchangeError (this.id + ' fetchBalance() / private API not implemented yet');
        //  JUNK FROM SOME OTHER EXCHANGE, TEMPLATE
        //  let response = await this.accountGetBalances ();
        //  let balances = response['result'];
        //  let result = { 'info': balances };
        //  let indexed = this.indexBy (balances, 'Currency');
        //  let keys = Object.keys (indexed);
        //  for (let i = 0; i < keys.length; i++) {
        //      let id = keys[i];
        //      let currency = this.commonCurrencyCode (id);
        //      let account = this.account ();
        //      let balance = indexed[id];
        //      let free = parseFloat (balance['Available']);
        //      let total = parseFloat (balance['Balance']);
        //      let used = total - free;
        //      account['free'] = free;
        //      account['used'] = used;
        //      account['total'] = total;
        //      result[currency] = account;
        //  }
        // return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenOrders (this.extend ({
            'symbol': market['id'],
        }, params));
        let orderbook = response['data'];
        return this.parseOrderBook (orderbook, undefined, 'BUY', 'SELL');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = ticker['datetime'];
        let symbol = undefined;
        if (market) {
            symbol = market['symbol'];
        } else {
            symbol = ticker['coinType'] + '/' + ticker['coinTypePair'];
        }
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'buy'),
            'ask': this.safeFloat (ticker, 'sell'),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': this.safeFloat (ticker, 'lastDealPrice'),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'vol'),
            'quoteVolume': this.safeFloat (ticker, 'volValue'),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        let response = await this.publicGetMarketOpenSymbols (params);
        let tickers = response['data'];
        let result = {};
        for (let t = 0; t < tickers.length; t++) {
            let ticker = this.parseTicker (tickers[t]);
            let symbol = ticker['symbol'];
            result[symbol] = ticker;
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenTick (this.extend ({
            'symbol': market['id'],
        }, params));
        let ticker = response['data'];
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = trade[0];
        let side = undefined;
        if (trade[1] == 'BUY') {
            side = 'buy';
        } else if (trade[1] == 'SELL') {
            side = 'sell';
        }
        return {
            'id': undefined,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': 'limit',
            'side': side,
            'price': trade[2],
            'amount': trade[3],
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOpenDealOrders (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    parseOHLCV (ohlcv, market = undefined, timeframe = '1d', since = undefined, limit = undefined) {
        let timestamp = this.parse8601 (ohlcv['T']);
        return [
            timestamp,
            ohlcv['O'],
            ohlcv['H'],
            ohlcv['L'],
            ohlcv['C'],
            ohlcv['V'],
        ];
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let to = this.seconds ();
        // whatever I try with from + to + limit it does not work (always an empty response)
        // tried all combinations:
        // - reversing them
        // - changing directions
        // - seconds
        // - milliseconds
        // - datetime strings
        // the endpoint doesn't seem to work, or something is missing in their docs
        let request = {
            'symbol': market['id'],
            'type': this.timeframes[timeframe],
            'from': to - 86400,
            'to': to,
        };
        if (since) {
            request['from'] = parseInt (since / 1000);
        }
        if (limit) {
            request['limit'] = limit;
        }
        let response = await this.publicGetOpenKline (this.extend (request, params));
        return this.parseOHLCVs (response['data'], market, timeframe, since, limit);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let endpoint = '/' + this.version + '/' + this.implodeParams (path, params);
        let url = this.urls['api'] + endpoint;
        let query = this.omit (params, this.extractParams (path));
        if (api == 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            throw new ExchangeError (this.id + ' private API not implemented yet');
            // ---------------------------------
            // FROM KUCOIN:
            // String host = "https://api.kucoin.com";
            // String endpoint = "/v1/KCS-BTC/order"; // API endpoint
            // String secret; // The secret assigned when the API created
            // POST parameters：
            //     type: BUY
            //     amount: 10
            //     price: 1.1
            //     Arrange the parameters in ascending alphabetical order (lower cases first), then combine them with & (don't urlencode them, don't add ?, don't add extra &), e.g. amount=10&price=1.1&type=BUY
            //     将查询参数按照字母升序(小字母在前)排列后用&进行连接(请不要进行urlencode操作,开头不要带?,首位不要有额外的&符号)得到的queryString如:  amount=10&price=1.1&type=BUY
            // String queryString;
            // // splice string for signing
            // String strForSign = endpoint + "/" + nonce + "/" + queryString;
            // // Make a Base64 encoding of the completed string
            // String signatureStr = Base64.getEncoder().encodeToString(strForSign.getBytes("UTF-8"));
            // // KC-API-SIGNATURE in header
            // String signatureResult = hmacEncrypt("HmacSHA256", signatureStr, secret);
            // ----------------------------------
            // TEMPLATE (it is close, but it still needs testing and debugging):
            this.checkRequiredCredentials ();
            // their nonce is always a calibrated synched milliseconds-timestamp
            let nonce = this.milliseconds ();
            let queryString = '';
            nonce = nonce.toString ();
            if (Object.keys (query).length) {
                queryString = this.rawencode (this.keysort (query));
                if (method == 'GET') {
                    url += '?' + queryString;
                } else {
                    body = queryString;
                }
            }
            let auth = endpoint + '/' + nonce + '/' + queryString;
            let payload = this.stringToBase64 (this.encode (auth));
            // payload should be "encoded" as returned from stringToBase64
            let signature = this.hmac (payload, this.encode (this.secret), 'sha512');
            headers = {
                'KC-API-KEY': this.apiKey (),
                'KC-API-NONCE': nonce,
                'KC-API-SIGNATURE': signature,
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body) {
        if (code >= 400) {
            if (body[0] == "{") {
                let response = JSON.parse (body);
                if ('success' in response) {
                    if (!response['success']) {
                        if ('message' in response) {
                            if (response['message'] == 'MIN_TRADE_REQUIREMENT_NOT_MET')
                                throw new InvalidOrder (this.id + ' ' + this.json (response));
                            if (response['message'] == 'APIKEY_INVALID')
                                throw new AuthenticationError (this.id + ' ' + this.json (response));
                        }
                        throw new ExchangeError (this.id + ' ' + this.json (response));
                    }
                }
            }
        }
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        return response;
    }
}
<|endoftext|>


<commit_before>(function(window, undefined) {

  var dloc = document.location;

  function regify(routes) { // convert all simple param routes to regex
    if (typeof routes === 'string') {
      return;
    }
    for (var key in routes) {
      regify(routes[key]);
      if (key.indexOf(':') !== -1) {
        var newKey = key.replace(/:.*?\/|:.*?$/g, '([a-zA-Z0-9-]+)/').slice(0, -1);
        routes[newKey] = routes[key];
        delete routes[key];
      }
    }
  }

  window.Router = Router;

  function Router(routes) {

    if(!(this instanceof Router)) return new Router(routes);

    var self = this;

    this.routes = routes;

    var add;

    this.recurse = function(value) {
      if (value === undefined) return recurse;
      add = (this._recurse = value) === 'forward' ? 'unshift' : 'push';
    };

    this._recurse = null;
    this.recurse(null);

    this.resource = null;
    this.state = {};
    this.after = [];
    this.on = [];
    this.once = [];
    this.oneach = [];
    this.aftereach = [];
    this.notfound = null;
    this.lastroutevalue = null;
    this.alreadyrun = false;

    regify(this.routes);

    function dispatch(src) {
      for (var i=0, l = self[src].length; i < l; i++) {

        var listener = self[src][i];
        var val = listener.val === null ? self.lastroutevalue : listener.val;

        if (typeof listener.fn === 'string') {
          listener.fn = self.resource[listener.fn];
        }

        if (typeof val === 'string') {
          val = [val];
        }

        if (listener.fn.apply(self.resource || null, val || []) === false) {
          self[src] = [];
          return false;
        }
        if (listener.val !== null) {
          self.lastroutevalue = listener.val;
        }

      }
    }

    function parse(routes, path, len, matched) {

      var roughmatch, exactmatch;
      var route = routes[path];

      if(!route || path === '/') {
        for (var r in routes) { // we dont have an exact match, lets explore.
          if(routes.hasOwnProperty(r)) {

            exactmatch = path.match(new RegExp('^' + r));
            roughmatch = path.match(new RegExp('^' + r + '(.*)?'));

            if(exactmatch && roughmatch) {

              // convert roughmatch to an array of names without `/`s.
              for (var i = roughmatch.length; i >= 0; i--) {
                if (roughmatch[i]) {
                  roughmatch[i] = roughmatch[i].replace(/^\//, '');
                }
                else {
                  roughmatch.splice(i, 1);
                }
              }

              if (exactmatch[0] === '/' &&
                  roughmatch.length > 1 &&
                  '/([a-zA-Z0-9-]+)' in routes) {
                continue;
              }

              var partsCount = exactmatch[0].split('/').length - 1,
                  _path = roughmatch.slice(exactmatch.length),
                  _len = len - partsCount,
                  _matched = matched.concat(exactmatch.slice(1));

              if (exactmatch.length > 1) {
                route = routes[r];
              }
              else {
                route = routes[exactmatch[0]];
              }

              if (next(_path, _len, _matched)) return true;
            }
          }
        }
      } else {
        _len = len - path.split('/').length + 1;
      }

      next('/', _len, matched);

      function next(path, len, matched) {
        if (route) {

          if(typeof path !== 'string') {
            path = '/' + path.join('/');
          }

          if (path !== '/') {
            parse(route, path, len, matched);
          }

          if (len === 0 || self._recurse) {

            function queue(fn, type) {
              if(fn && typeof fn !== 'string' && fn[0]) {
                for (var j = 0, m = fn.length; j < m; j++) {
                  self[type][add]({ fn: fn[j], val: matched || path });
                }
              }
              else {
                self[type][add]({ fn: fn, val: matched || path });
              }
            };

            if (typeof route === 'function' || route.on) {
              queue(route.on || route, 'on');
            }

            if (route.once && !route.once._fired){
              route.once._fired = true;
              queue(route.once, 'once');
            }

            if (route.after){
              queue(route.after, 'after');
            }

            return true;
          }
        }
        else {
          self.noroute(matched);
        }
      };
      return true;
    }

    function route(event) {

      var loc = dloc.hash.slice(1);
      var len = loc.split('/').length-1;

      self.after = [];

      if(parse(self.routes, loc, len, [])) {
        dispatch('on');
        dispatch('once');
        dispatch('oneach');
        self.on = [];
        self.once = [];
      }

      dispatch('after');
      dispatch('aftereach');
    }

    this.init = function(r) {

      if(dloc.hash === '/' && r) {
        dloc.hash = r;
      }

      if(dloc.hash.length > 0) {
        route();
      }

      listener.init(route);
      return this;
    };

    this.destroy = function() {
      listener.destroy(route);
      return this;
    };

    return this;
  }

  Router.prototype.use = function(conf) {

    for(var item in conf) {
      if(conf.hasOwnProperty(item)) {

        if(item === 'recurse') {
          this.recurse(conf[item]);
          continue;
        }

        if(item === 'resource') {
          this.resource = conf[item];
          continue;
        }

        if(item === 'notfound') {
          this.notfound = conf[item];
          continue;
        }

        var fn = conf[item];
        var store = null;
        var type = ({}).toString.call(fn);

        if(item === 'on') { store = 'oneach'; }
        if(item === 'after') { store = 'aftereach'; }

        if(type.indexOf('Array') !== -1) {
          for (var i=0, l = fn.length; i < l; i++) {
            this[store].push({ fn: fn[i], val: null });
          }
        }
        else {
          this[store].push({ fn: fn, val: null });
        }
      }
    }
    return this;
  };

  Router.prototype.noroute = function(routename) {
    if(this.notfound) {
      if(({}).toString.call(this.notfound).indexOf('Array') !== -1) {
        for (var i=0, l = this.notfound.length; i < l; i++) {
          this.notfound[i](routename);
        }
      }
      else {
          this.notfound(routename);
      }
    }
  };

  Router.prototype.explode = function() {
    var v = dloc.hash;
    if(v[1] === '/') { v=v.slice(1); }
    return v.slice(1, v.length).split("/");
  };

  Router.prototype.setRoute = function(i, v, val) {

    var url = this.explode();

    if(typeof i === 'number' && typeof v === 'string') {
      url[i] = v;
    }
    else if(typeof val === 'string') {
      url.splice(i, v, s);
    }
    else {
      url = [i];
    }

    listener.setHash(url.join('/'));
    return url;
  };

  Router.prototype.getState = function() {
    return this.state;
  };

  Router.prototype.getRoute = function(v) {

    var ret = v;

    if(typeof v === "number") {
      ret = this.explode()[v];
    }
    else if(typeof v === "string"){
      var h = this.explode();
      ret = h.indexOf(v);
    }
    else {
      ret = this.explode();
    }

    return ret;
  };

  var version = '0.4.0',
      mode = 'modern',
      listener = {

    hash: dloc.hash,

    check:  function () {
      var h = dloc.hash;
      if (h != this.hash) {
        this.hash = h;
        this.onHashChanged();
      }
    },

    fire: function() {
      if(mode === 'modern') {
        window.onhashchange();
      }
      else {
        this.onHashChanged();
      }
    },

    init: function (fn) {

      var self = this;

      if(!window.Router.listeners) {
        window.Router.listeners = [];
      }

      function onchange() {
        for(var i = 0, l = window.Router.listeners.length; i < l; i++) {
          window.Router.listeners[i]();
        }
      }

      //note IE8 is being counted as 'modern' because it has the hashchange event
      if('onhashchange' in window &&
          (document.documentMode === undefined || document.documentMode > 7)) {
        window.onhashchange = onchange;
        mode = 'modern';
      }
      else { // IE support, based on a concept by Erik Arvidson ...

        var frame = document.createElement('iframe');
        frame.id = 'state-frame';
        frame.style.display = 'none';
        document.body.appendChild(frame);
        this.writeFrame('');

        if ('onpropertychange' in document && 'attachEvent' in document) {
          document.attachEvent('onpropertychange', function () {
            if (event.propertyName === 'location') {
              self.check();
            }
          });
        }

        window.setInterval(function () { self.check(); }, 50);

        this.onHashChanged = onchange;
        mode = 'legacy';
      }

      window.Router.listeners.push(fn);

      return mode;
    },

    destroy: function (fn) {
      if (!window.Router || !window.Router.listeners) return;

      var listeners = window.Router.listeners;

      for (var i = listeners.length - 1; i >= 0; i--) {
        if (listeners[i] === fn) {
          listeners.splice(i, 1);
        }
      }
    },

    setHash: function (s) {

      // Mozilla always adds an entry to the history
      if (mode === 'legacy') {
        this.writeFrame(s);
      }
      dloc.hash = (s[0] === '/') ? s : '/' + s;
      return this;
    },

    writeFrame: function (s) {
      // IE support...
      var f = document.getElementById('state-frame');
      var d = f.contentDocument || f.contentWindow.document;
      d.open();
      d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
      d.close();
    },

    syncHash: function () {
      // IE support...
      var s = this._hash;
      if (s != dloc.hash) {
        dloc.hash = s;
      }
      return this;
    },

    onHashChanged:  function () {}
  };

}(window));
<commit_msg>[lib] a top level route such as '/' should not override a tokenized route at the same level. [test] a test was added to support this library change<commit_after>(function(window, undefined) {

  var dloc = document.location;

  function regify(routes) { // convert all simple param routes to regex
    if (typeof routes === 'string') {
      return;
    }
    for (var key in routes) {
      regify(routes[key]);
      if (key.indexOf(':') !== -1) {
        var newKey = key.replace(/:.*?\/|:.*?$/g, '([a-zA-Z0-9-]+)/').slice(0, -1);
        routes[newKey] = routes[key];
        delete routes[key];
      }
    }
  }

  window.Router = Router;

  function Router(routes) {

    if(!(this instanceof Router)) return new Router(routes);

    var self = this;

    this.routes = routes;

    var add;

    this.recurse = function(value) {
      if (value === undefined) return recurse;
      add = (this._recurse = value) === 'forward' ? 'unshift' : 'push';
    };

    this._recurse = null;
    this.recurse(null);

    this.resource = null;
    this.state = {};
    this.after = [];
    this.on = [];
    this.once = [];
    this.oneach = [];
    this.aftereach = [];
    this.notfound = null;
    this.lastroutevalue = null;
    this.alreadyrun = false;

    regify(this.routes);

    function dispatch(src) {
      for (var i=0, l = self[src].length; i < l; i++) {

        var listener = self[src][i];
        var val = listener.val === null ? self.lastroutevalue : listener.val;

        if (typeof listener.fn === 'string') {
          listener.fn = self.resource[listener.fn];
        }

        if (typeof val === 'string') {
          val = [val];
        }

        if (listener.fn.apply(self.resource || null, val || []) === false) {
          self[src] = [];
          return false;
        }
        if (listener.val !== null) {
          self.lastroutevalue = listener.val;
        }

      }
    }

    function parse(routes, path, len, matched) {

      var roughmatch, exactmatch;
      var route = routes[path];

      if(!route || path === '/') {
        for (var r in routes) { // we dont have an exact match, lets explore.
          if(routes.hasOwnProperty(r)) {

            exactmatch = path.match(new RegExp('^' + r));
            roughmatch = path.match(new RegExp('^' + r + '(.*)?'));

            if(exactmatch && roughmatch) {

              // convert roughmatch to an array of names without `/`s.
              for (var i = roughmatch.length; i >= 0; i--) {
                if (roughmatch[i]) {
                  roughmatch[i] = roughmatch[i].replace(/^\//, '');
                }
                else {
                  roughmatch.splice(i, 1);
                }
              }

              if (exactmatch[0] === '/' &&
                  roughmatch.length > 1 &&
                  '/([a-zA-Z0-9-]+)' in routes) {
                continue;
              }

              var partsCount = exactmatch[0].split('/').length - 1,
                  _path = roughmatch.slice(exactmatch.length),
                  _len = len - partsCount,
                  _matched = matched.concat(exactmatch.slice(1));

              if (exactmatch.length > 1) {
                route = routes[r];
              }
              else {
                route = routes[exactmatch[0]];
              }

              if (next(_path, _len, _matched)) return true;
            }
          }
        }
      } else {
        _len = len - path.split('/').length + 1;
      }

      next('/', _len, matched);

      function next(path, len, matched) {
        if (route) {

          if(typeof path !== 'string') {
            path = '/' + path.join('/');
          }

          if (path !== '/') {
            parse(route, path, len, matched);
          }

          if (len === 0 || self._recurse) {

            function queue(fn, type) {
              if(fn && typeof fn !== 'string' && fn[0]) {
                for (var j = 0, m = fn.length; j < m; j++) {
                  self[type][add]({ fn: fn[j], val: matched || path });
                }
              }
              else {
                self[type][add]({ fn: fn, val: matched || path });
              }
            };

            if (typeof route === 'function' || route.on) {
              queue(route.on || route, 'on');
            }

            if (route.once && !route.once._fired){
              route.once._fired = true;
              queue(route.once, 'once');
            }

            if (route.after){
              queue(route.after, 'after');
            }

            return true;
          }
        }
        else {
          self.noroute(matched);
        }
      };
      return true;
    }

    function route(event) {

      var loc = dloc.hash.slice(1);
      var len = loc.split('/').length-1;

      self.after = [];

      if(parse(self.routes, loc, len, [])) {
        dispatch('on');
        dispatch('once');
        dispatch('oneach');
        self.on = [];
        self.once = [];
      }

      dispatch('after');
      dispatch('aftereach');
    }

    this.init = function(r) {

      if(dloc.hash === '' && r) {
        dloc.hash = r;
      }

      if(dloc.hash.length > 0) {
        route();
      }

      listener.init(route);
      return this;
    };

    this.destroy = function() {
      listener.destroy(route);
      return this;
    };

    return this;
  }

  Router.prototype.use = function(conf) {

    for(var item in conf) {
      if(conf.hasOwnProperty(item)) {

        if(item === 'recurse') {
          this.recurse(conf[item]);
          continue;
        }

        if(item === 'resource') {
          this.resource = conf[item];
          continue;
        }

        if(item === 'notfound') {
          this.notfound = conf[item];
          continue;
        }

        var fn = conf[item];
        var store = null;
        var type = ({}).toString.call(fn);

        if(item === 'on') { store = 'oneach'; }
        if(item === 'after') { store = 'aftereach'; }

        if(type.indexOf('Array') !== -1) {
          for (var i=0, l = fn.length; i < l; i++) {
            this[store].push({ fn: fn[i], val: null });
          }
        }
        else {
          this[store].push({ fn: fn, val: null });
        }
      }
    }
    return this;
  };

  Router.prototype.noroute = function(routename) {
    if(this.notfound) {
      if(({}).toString.call(this.notfound).indexOf('Array') !== -1) {
        for (var i=0, l = this.notfound.length; i < l; i++) {
          this.notfound[i](routename);
        }
      }
      else {
          this.notfound(routename);
      }
    }
  };

  Router.prototype.explode = function() {
    var v = dloc.hash;
    if(v[1] === '/') { v=v.slice(1); }
    return v.slice(1, v.length).split("/");
  };

  Router.prototype.setRoute = function(i, v, val) {

    var url = this.explode();

    if(typeof i === 'number' && typeof v === 'string') {
      url[i] = v;
    }
    else if(typeof val === 'string') {
      url.splice(i, v, s);
    }
    else {
      url = [i];
    }

    listener.setHash(url.join('/'));
    return url;
  };

  Router.prototype.getState = function() {
    return this.state;
  };

  Router.prototype.getRoute = function(v) {

    var ret = v;

    if(typeof v === "number") {
      ret = this.explode()[v];
    }
    else if(typeof v === "string"){
      var h = this.explode();
      ret = h.indexOf(v);
    }
    else {
      ret = this.explode();
    }

    return ret;
  };

  var version = '0.4.0',
      mode = 'modern',
      listener = {

    hash: dloc.hash,

    check:  function () {
      var h = dloc.hash;
      if (h != this.hash) {
        this.hash = h;
        this.onHashChanged();
      }
    },

    fire: function() {
      if(mode === 'modern') {
        window.onhashchange();
      }
      else {
        this.onHashChanged();
      }
    },

    init: function (fn) {

      var self = this;

      if(!window.Router.listeners) {
        window.Router.listeners = [];
      }

      function onchange() {
        for(var i = 0, l = window.Router.listeners.length; i < l; i++) {
          window.Router.listeners[i]();
        }
      }

      //note IE8 is being counted as 'modern' because it has the hashchange event
      if('onhashchange' in window &&
          (document.documentMode === undefined || document.documentMode > 7)) {
        window.onhashchange = onchange;
        mode = 'modern';
      }
      else { // IE support, based on a concept by Erik Arvidson ...

        var frame = document.createElement('iframe');
        frame.id = 'state-frame';
        frame.style.display = 'none';
        document.body.appendChild(frame);
        this.writeFrame('');

        if ('onpropertychange' in document && 'attachEvent' in document) {
          document.attachEvent('onpropertychange', function () {
            if (event.propertyName === 'location') {
              self.check();
            }
          });
        }

        window.setInterval(function () { self.check(); }, 50);

        this.onHashChanged = onchange;
        mode = 'legacy';
      }

      window.Router.listeners.push(fn);

      return mode;
    },

    destroy: function (fn) {
      if (!window.Router || !window.Router.listeners) return;

      var listeners = window.Router.listeners;

      for (var i = listeners.length - 1; i >= 0; i--) {
        if (listeners[i] === fn) {
          listeners.splice(i, 1);
        }
      }
    },

    setHash: function (s) {

      // Mozilla always adds an entry to the history
      if (mode === 'legacy') {
        this.writeFrame(s);
      }
      dloc.hash = (s[0] === '/') ? s : '/' + s;
      return this;
    },

    writeFrame: function (s) {
      // IE support...
      var f = document.getElementById('state-frame');
      var d = f.contentDocument || f.contentWindow.document;
      d.open();
      d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
      d.close();
    },

    syncHash: function () {
      // IE support...
      var s = this._hash;
      if (s != dloc.hash) {
        dloc.hash = s;
      }
      return this;
    },

    onHashChanged:  function () {}
  };

}(window));
<|endoftext|>


<commit_before>var app = angular.module("loklak", ['ngRoute']);
app.controller("status", function ($scope, $http) {
  $http.get("http://loklak.org/api/status.json").
    success(function (data, status, headers, config) {
      $scope.index = data.index;
    });
});

app.controller("search", function ($scope, $http) {
  $scope.query = '';
  $scope.results = [];
  $scope.search = function () {
    if ($scope.query != '') {
      $scope.results = [];
      $http.get("http://loklak.org/api/search.json?q=" + $scope.query).
        success(function (data, status, headers, config) {
          for (var i = 0; i < data.statuses.length; i++) {
            $scope.results.push(data.statuses[i].text);
          }
        });
    }
  }
});

app.filter("reverse", function () {
  return function (items) {
    if (!items || !items.length) {
      return;
    }
    return items.slice().reverse();
  };
});

angular.element(document).ready(function () {
  $(".menu-btn").click(function () {
    //$("#navSidebar").show();
  });
  var navString = "";
  var winLocation = window.location.href;
  $.getJSON("/cms/topmenu.json", function (data) {
    navItems = data.items;
    navItems = navItems.reverse();
    var count = 0;
    $.each( navItems, function (index, itemData) {
      name = Object.keys(itemData);
      link = itemData[name];
      // Now construct the li items
      liItem = "
";
      if (winLocation.indexOf(link) != -1 && count != 1) {
        liItem = "
";
        count = count + 1;
      }

        liItem += ""+name+"
";
      liItem = $(liItem);
      $('#navbar > ul').prepend(liItem);
    });
  });
});

var lastScrollTop = $(window).scrollTop();
$(window).scroll(function(event){
  var st = $(this).scrollTop();
  if (st <= 100){
    $('nav').animate({
      top: "0"
    }, 50);
    $('.sidebar').animate({
      top: "7.33%"
    }, 50);
  }
  else if(lastScrollTop <= 100 && st > lastScrollTop){
    $('nav').animate({
      top: "-20%"
    }, 70);
    $('.sidebar').animate({
      top: "-5%"
    }, 70);
  }
  lastScrollTop = st;
});
<commit_msg>Hidden on top scrolling<commit_after>var app = angular.module("loklak", ['ngRoute']);
app.controller("status", function ($scope, $http) {
  $http.get("http://loklak.org/api/status.json").
    success(function (data, status, headers, config) {
      $scope.index = data.index;
    });
});

app.controller("search", function ($scope, $http) {
  $scope.query = '';
  $scope.results = [];
  $scope.search = function () {
    if ($scope.query != '') {
      $scope.results = [];
      $http.get("http://loklak.org/api/search.json?q=" + $scope.query).
        success(function (data, status, headers, config) {
          for (var i = 0; i < data.statuses.length; i++) {
            $scope.results.push(data.statuses[i].text);
          }
        });
    }
  }
});

app.filter("reverse", function () {
  return function (items) {
    if (!items || !items.length) {
      return;
    }
    return items.slice().reverse();
  };
});

angular.element(document).ready(function () {
  $(".menu-btn").click(function () {
    //$("#navSidebar").show();
  });
  var navString = "";
  var winLocation = window.location.href;
  $.getJSON("/cms/topmenu.json", function (data) {
    navItems = data.items;
    navItems = navItems.reverse();
    var count = 0;
    $.each( navItems, function (index, itemData) {
      name = Object.keys(itemData);
      link = itemData[name];
      // Now construct the li items
      liItem = "
";
      if (winLocation.indexOf(link) != -1 && count != 1) {
        liItem = "
";
        count = count + 1;
      }

        liItem += ""+name+"
";
      liItem = $(liItem);
      $('#navbar > ul').prepend(liItem);
    });
  });
});

var lastScrollTop = $(window).scrollTop();
$(window).scroll(function(event){
  var st = $(this).scrollTop();
  if (st <= 100){
    $('nav').animate({
      top: "0"
    }, 40);
    $('.sidebar').animate({
      top: "7.4%"
    }, 40);
  }
  else if(lastScrollTop <= 100 && st > lastScrollTop){
    $('nav').animate({
      top: "-20%"
    }, 35);
    $('.sidebar').animate({
      top: "-5%"
    }, 50);
  }
  lastScrollTop = st;
});
<|endoftext|>