<commit_before>/*
WYSIWYG arguments

enableKeyboardExtras {boolean} enable creating new components on enter, and appending text to previous components on delete, etc
buttons {array} array of button names (strings) for tooltip
styled {boolean} apply input styles to contenteditable element
 */

var _ = require('lodash'),
  select = require('selection-range'),
  MediumEditor = require('medium-editor'),
  MediumButton = require('../services/medium-button'),
  dom = require('../services/dom'),
  db = require('../services/db'),
  render = require('../services/render'),
  focus = require('../decorators/focus'),
  references = require('../services/references'),
  edit = require('../services/edit'),
  model = require('../services/model-text'),
  getInput = require('../services/get-input'),
  refAttr = references.referenceAttribute;

/**
 * toggle the tiered toolbar
 * this is the action for the tieredToolbar extension
 * @param {Element} html
 * @returns {Element}
 */
function toggleTieredToolbar(html) {
  // note: this element doesn't exist before it's instantiated, so we need to grab it afterwards
  var toolbar = dom.find('.medium-editor-toolbar'),
    toolbarClasses = toolbar.classList;

  if (toolbarClasses.contains('show-none')) {
    toolbarClasses.remove('show-none');
  } else if (toolbarClasses.contains('show-all')) {
    toolbarClasses.add('show-none');
  } else {
    toolbarClasses.add('show-all');
  }
  return html;
}

/**
 * create new medium editor
 * @param {Element} field
 * @param {array} buttons
 * @returns {Element}
 */
function createEditor(field, buttons) {
  return new MediumEditor(field, {
    toolbar: {
      // buttons that go in the toolbar
      buttons: buttons,
      standardizeSelectionStart: true,
      allowMultiParagraphSelection: false
    },
    delay: 200, // wait a bit for the toolbar and link previews to display
    paste: {
      forcePlainText: false,
      cleanPastedHTML: true, // clean html from sources like google docs
      cleanTags: [ // remove these tags when pasting
        'meta',
        'script',
        'style',
        'img',
        'object',
        'iframe'
      ],
      cleanReplacements: [
        [/<h[1-9]>/ig, '
'],
        [/<\/h[1-9]>/ig, '
'], // force all headers to the same level
        [/
/ig, ''], // get rid of
 tags
        [/<\/p>/ig, '']
      ]
    },
    autoLink: false, // create links automatically when urls are entered
    imageDragging: false, // disallow dragging inline images
    targetBlank: true,
    disableReturn: true,
    placeholder: false, // the placeholder isn't native
    extensions: {
      tieredToolbar: new MediumButton({
        label: '…',
        action: toggleTieredToolbar
      })
    }
  });
}

/**
 * get elements and data for the current component
 * @param {Element} el
 * @returns {object}
 */
function getCurrent(el) {
  var currentComponent = dom.closest(el, '[' + refAttr + ']'),
    currentComponentRef = currentComponent.getAttribute(refAttr);

  return {
    field: el.getAttribute(references.fieldAttribute),
    component: currentComponent,
    ref: currentComponentRef,
    name: references.getComponentNameFromReference(currentComponentRef)
  };
}

/**
 * get elements and data for the parent component
 * @param {Element} el of the current component
 * @returns {object}
 */
function getParent(el) {
  var parentNode = el.parentNode,
    parentComponent = dom.closest(parentNode, '[' + refAttr + ']'),
    parentComponentRef = parentComponent.getAttribute(refAttr);

  return {
    field: dom.closest(parentNode, '[' + references.editableAttribute + ']').getAttribute(references.editableAttribute),
    component: parentComponent,
    ref: parentComponentRef,
    name: references.getComponentNameFromReference(parentComponentRef)
  };
}

/**
 * get previous component in list, if any
 * @param {object} current
 * @param {object} parent
 * @returns {Promise} with {_ref: previous ref} or undefined
 */
function getPrev(current, parent) {
  return edit.getDataOnly(parent.ref).then(function (parentData) {
    var index = _.findIndex(parentData[parent.field], { _ref: current.ref }),
      before = _.take(parentData[parent.field], index),
      prev = _.findLast(before, function (component) {
        return references.getComponentNameFromReference(component._ref) === current.name;
      });

    if (prev) {
      return {
        field: current.field,
        component: dom.find(parent.component, '[' + refAttr + '="' + prev._ref + '"]'),
        ref: prev._ref,
        name: current.name
      };
    }
  });
}

/**
 * get the contents of a wysiwyg field
 * note: if we want to remove / parse / sanitize contents when doing operations,
 * this is the place we should do it
 * @param {Element} el
 * @returns {string}
 */
function getFieldContents(el) {
  return el.innerHTML;
}

/**
 * append text/html to previous component's field
 * @param {string} html
 * @param {object} prev
 * @returns {Promise}
 */
function appendToPrev(html, prev) {
// note: get fresh data from the server
  return db.getComponentJSONFromReference(prev.ref).then(function (prevData) {
    var prevFieldData = _.get(prevData, prev.field),
      throwawayDiv = document.createElement('div'),
      textmodel, fragment, cleanedString;

    // add current field's html to the end of the previous field
    prevFieldData += html;

    // pass the full thing through text-model to clean it and merge tags
    textmodel = model.fromElement(dom.create(prevFieldData));
    fragment = model.toElement(textmodel);
    throwawayDiv.appendChild(fragment);
    cleanedString = throwawayDiv.innerHTML;

    // then put it back into the previous component's data
    _.set(prevData, prev.field, cleanedString);
    return edit.update(prev.ref, prevData);
  });
}

/**
 * remove current component from parent
 * @param {object} current
 * @param {object} parent
 * @returns {Function}
 */
function removeCurrentFromParent(current, parent) {
  return function () {
    return edit.removeFromParentList({el: current.component, ref: current.ref, parentField: parent.field, parentRef: parent.ref});
  };
}

/**
 * focus on the previous component's field
 * @param {object} parent
 * @param  {object} prev
 * @param {number} textLength
 * @returns {Function}
 */
function focusPreviousComponent(parent, prev, textLength) {
  return function () {
    var newEl = dom.find(parent.component, '[' + references.referenceAttribute + '="' + prev.ref + '"]');

    return focus.focus(newEl, { ref: prev.ref, path: prev.field }).then(function (prevField) {
      // set caret right before the new text we added
      select(prevField, { start: prevField.textContent.length - (textLength + 1) });
    });
  };
}

/**
 * remove current component, append text to previous component (of the same name)
 * @param {Element} el
 * @returns {Promise|undefined}
 */
function removeComponent(el) {
  var current = getCurrent(el),
    parent = getParent(current.component),
    textLength = el.innerText.length;

  // find the previous component, if any
  return getPrev(current, parent).then(function (prev) {
    if (prev) {
      // there's a previous component with the same name!
      // get the contents of the current field, and append them to the previous component
      return appendToPrev(getFieldContents(el), prev)
        .then(removeCurrentFromParent(current, parent))
        .then(render.reloadComponent.bind(null, prev.ref))
        .then(focusPreviousComponent(parent, prev, textLength));
    }
  });
}

/**
 * add component after current component
 * @param {Element} el
 * @param {string} [text]
 * @returns {Promise}
 */
function addComponent(el, text) {
  var current = getCurrent(el),
    parent = getParent(current.component),
    newData = {};

  // if we're passing data in, set it into the object
  if (text) {
    newData[current.field] = text;
  }

  return edit.createComponent(current.name, newData)
    .then(function (res) {
      var newRef = res._ref;

      return edit.addToParentList({ref: newRef, prevRef: current.ref, parentField: parent.field, parentRef: parent.ref})
        .then(function (newEl) {
          dom.insertAfter(current.component, newEl);
          return render.addComponentsHandlers(newEl)
            .then(function () {
              // focus on the same field in the new component
              focus.focus(newEl, { ref: newRef, path: current.field }).then(function () {
                return newRef;
              });
            });
        });
    });
}

/**
 * split text in a component, creating a new component
 * @param {Element} el
 * @param {object} caret
 * @param {object} observer used to update the current component
 * @returns {Promise}
 */
function splitComponent(el, caret, observer) {
  var textmodel = model.fromElement(dom.create(el.innerHTML.replace(/ /g, ' '))),
    // note: we're removing any nonbreaking spaces BEFORE parsing the text
    splitText = model.split(textmodel, caret.start),
    oldText = splitText[0],
    newText = splitText[1],
    oldFragment = model.toElement(oldText),
    newFragment = model.toElement(newText),
    throwawayDiv = document.createElement('div');

  // to get the innerHTML of the document fragment,
  // we first need to append it to a throwaway div.
  // this is not awesome, but is the "best practice"
  throwawayDiv.appendChild(newFragment);

  // now that we have the split elements, put the old one back in and then create the new one
  dom.clearChildren(el); // clear the current children
  el.appendChild(oldFragment); // add the cleaned dom fragment
  observer.setValue(el.innerHTML); // update the current field
  // this is saved automatically when it's unfocused
  return addComponent(el, throwawayDiv.innerHTML);
}

/**
 * remove current component if we're at the beginning of the field
 * and there's a previous component to append it to
 * @param {Element} el
 * @param {KeyboardEvent} e
 * @returns {undefined|Promise}
 */
function handleComponentDeletion(el, e) {
  var caretPos = select(el);

  if (caretPos.start === 0) {
    e.preventDefault(); // stop page reload
    return removeComponent(el);
  }
}

/**
 * create new component if we're at the end of the field
 * @param {Element} el
 * @param {object} observer
 * @returns {false|Promise}
 */
function handleComponentCreation(el, observer) {
  var caretPos = select(el); // get text after the cursor, if any

  // if there's stuff after the caret, get it
  if (caretPos.start < el.textContent.length - 1) {
    return splitComponent(el, caretPos, observer);
  } else {
    return addComponent(el);
  }
}

function isStyled(styled) {
  return styled ? ' styled' : ''; // note the preeeding space!
}

function addLineBreak() {
  var selection = window.getSelection(),
    range = selection.getRangeAt(0),
    br = document.createElement('br');

  // woah, crazy stuff!
  // this creates a range, inserts the
 tag, then sets the caret after it
  range.deleteContents();
  range.insertNode(br);
  range.setStartAfter(br);
  range.setEndAfter(br);
  range.collapse(false);
  selection.removeAllRanges();
  selection.addRange(range);
}

module.exports = function (result, args) {
  var rivets = result.rivets,
    buttons = args.buttons,
    styled = args.styled,
    enableKeyboardExtras = args.enableKeyboardExtras,
    textInput = getInput(result.el),
    field = dom.create(`<p class="wysiwyg-input${ isStyled(styled) }" data-field="${result.bindings.name}" rv-wysiwyg="data.value"></p>`);

  // if more than 5 buttons, put the rest on the second tier
  if (buttons.length > 5) {
    buttons.splice(5, 0, 'tieredToolbar'); // clicking this expands the toolbar with a second tier
  }

  // put the rich text field after the input
  dom.replaceElement(textInput, field);

  rivets.binders.wysiwyg = {
    publish: true,
    bind: function (el) {
      // this is called when the binder initializes
      var observer = this.observer,
        data = observer.value() || '', // don't print 'undefined' if there's no data
        editor = createEditor(field, buttons),
        italicBtn = dom.find('.medium-editor-action-italic'),
        strikeBtn = dom.find('.medium-editor-action-strikethrough'),
        linkBtn = dom.find('.medium-editor-action-anchor');

      // put the initial data into the editor
      el.innerHTML = data;

      // apply custom styling to buttons
      if (italicBtn) {
        italicBtn.innerHTML = 'I';
      }
      if (strikeBtn) {
        strikeBtn.innerHTML = '<s>M</s>';
      }
      if (linkBtn) {
        linkBtn.innerHTML = `<img src="/media/components/byline-editor/link-icon.svg" />`;
      }

      // hide the tier2 buttons when closing the toolbar
      editor.subscribe('hideToolbar', function () {
        dom.find('.medium-editor-toolbar').classList.remove('show-all');
        dom.find('.medium-editor-toolbar').classList.remove('show-none');
      });

      // persist editor data to data model on input
      editor.subscribe('editableInput', function (e, editable) {
        observer.setValue(editable.innerHTML);
      });

      // persist editor data to data model on paste
      editor.subscribe('editablePaste', function (e, editable) {
        var textmodel = model.fromElement(dom.create(editable.innerHTML)),
          fragment = model.toElement(textmodel);

        // note: we're using the model-text service to clean up and standardize html
        // this is in addition to the cleanup that medium-editor does by default,
        // as well as the options we specified in the cleanTags / cleanReplacements paste config

        dom.clearChildren(editable); // clear the current children
        editable.appendChild(fragment); // add the cleaned dom fragment
        observer.setValue(editable.innerHTML);
      });

      editor.subscribe('editableKeydownDelete', function (e, editable) {
        if (enableKeyboardExtras) {
          handleComponentDeletion(editable, e);
        }
      });

      editor.subscribe('editableKeydownEnter', function (e, editable) {
        if (enableKeyboardExtras && e.shiftKey) {
          // shift+enter was pressed. add a line break
          addLineBreak();
        } else if (enableKeyboardExtras) {
          // enter was pressed. create a new component if certain conditions are met
          handleComponentCreation(editable, observer);
        } else {
          // close the form?
          focus.unfocus();
        }
      });
    }
  };

  return result;
};
<commit_msg>fixed caret issue<commit_after>/*
WYSIWYG arguments

enableKeyboardExtras {boolean} enable creating new components on enter, and appending text to previous components on delete, etc
buttons {array} array of button names (strings) for tooltip
styled {boolean} apply input styles to contenteditable element
 */

var _ = require('lodash'),
  select = require('selection-range'),
  MediumEditor = require('medium-editor'),
  MediumButton = require('../services/medium-button'),
  dom = require('../services/dom'),
  db = require('../services/db'),
  render = require('../services/render'),
  focus = require('../decorators/focus'),
  references = require('../services/references'),
  edit = require('../services/edit'),
  model = require('../services/model-text'),
  getInput = require('../services/get-input'),
  refAttr = references.referenceAttribute;

/**
 * toggle the tiered toolbar
 * this is the action for the tieredToolbar extension
 * @param {Element} html
 * @returns {Element}
 */
function toggleTieredToolbar(html) {
  // note: this element doesn't exist before it's instantiated, so we need to grab it afterwards
  var toolbar = dom.find('.medium-editor-toolbar'),
    toolbarClasses = toolbar.classList;

  if (toolbarClasses.contains('show-none')) {
    toolbarClasses.remove('show-none');
  } else if (toolbarClasses.contains('show-all')) {
    toolbarClasses.add('show-none');
  } else {
    toolbarClasses.add('show-all');
  }
  return html;
}

/**
 * create new medium editor
 * @param {Element} field
 * @param {array} buttons
 * @returns {Element}
 */
function createEditor(field, buttons) {
  return new MediumEditor(field, {
    toolbar: {
      // buttons that go in the toolbar
      buttons: buttons,
      standardizeSelectionStart: true,
      allowMultiParagraphSelection: false
    },
    delay: 200, // wait a bit for the toolbar and link previews to display
    paste: {
      forcePlainText: false,
      cleanPastedHTML: true, // clean html from sources like google docs
      cleanTags: [ // remove these tags when pasting
        'meta',
        'script',
        'style',
        'img',
        'object',
        'iframe'
      ],
      cleanReplacements: [
        [/<h[1-9]>/ig, '
'],
        [/<\/h[1-9]>/ig, '
'], // force all headers to the same level
        [/
/ig, ''], // get rid of
 tags
        [/<\/p>/ig, '']
      ]
    },
    autoLink: false, // create links automatically when urls are entered
    imageDragging: false, // disallow dragging inline images
    targetBlank: true,
    disableReturn: true,
    placeholder: false, // the placeholder isn't native
    extensions: {
      tieredToolbar: new MediumButton({
        label: '…',
        action: toggleTieredToolbar
      })
    }
  });
}

/**
 * get elements and data for the current component
 * @param {Element} el
 * @returns {object}
 */
function getCurrent(el) {
  var currentComponent = dom.closest(el, '[' + refAttr + ']'),
    currentComponentRef = currentComponent.getAttribute(refAttr);

  return {
    field: el.getAttribute(references.fieldAttribute),
    component: currentComponent,
    ref: currentComponentRef,
    name: references.getComponentNameFromReference(currentComponentRef)
  };
}

/**
 * get elements and data for the parent component
 * @param {Element} el of the current component
 * @returns {object}
 */
function getParent(el) {
  var parentNode = el.parentNode,
    parentComponent = dom.closest(parentNode, '[' + refAttr + ']'),
    parentComponentRef = parentComponent.getAttribute(refAttr);

  return {
    field: dom.closest(parentNode, '[' + references.editableAttribute + ']').getAttribute(references.editableAttribute),
    component: parentComponent,
    ref: parentComponentRef,
    name: references.getComponentNameFromReference(parentComponentRef)
  };
}

/**
 * get previous component in list, if any
 * @param {object} current
 * @param {object} parent
 * @returns {Promise} with {_ref: previous ref} or undefined
 */
function getPrev(current, parent) {
  return edit.getDataOnly(parent.ref).then(function (parentData) {
    var index = _.findIndex(parentData[parent.field], { _ref: current.ref }),
      before = _.take(parentData[parent.field], index),
      prev = _.findLast(before, function (component) {
        return references.getComponentNameFromReference(component._ref) === current.name;
      });

    if (prev) {
      return {
        field: current.field,
        component: dom.find(parent.component, '[' + refAttr + '="' + prev._ref + '"]'),
        ref: prev._ref,
        name: current.name
      };
    }
  });
}

/**
 * get the contents of a wysiwyg field
 * note: if we want to remove / parse / sanitize contents when doing operations,
 * this is the place we should do it
 * @param {Element} el
 * @returns {string}
 */
function getFieldContents(el) {
  return el.innerHTML;
}

/**
 * append text/html to previous component's field
 * @param {string} html
 * @param {object} prev
 * @returns {Promise}
 */
function appendToPrev(html, prev) {
// note: get fresh data from the server
  return db.getComponentJSONFromReference(prev.ref).then(function (prevData) {
    var prevFieldData = _.get(prevData, prev.field),
      throwawayDiv = document.createElement('div'),
      textmodel, fragment, cleanedString;

    // add current field's html to the end of the previous field
    prevFieldData += html;

    // pass the full thing through text-model to clean it and merge tags
    textmodel = model.fromElement(dom.create(prevFieldData));
    fragment = model.toElement(textmodel);
    throwawayDiv.appendChild(fragment);
    cleanedString = throwawayDiv.innerHTML;

    // then put it back into the previous component's data
    _.set(prevData, prev.field, cleanedString);
    return edit.update(prev.ref, prevData);
  });
}

/**
 * remove current component from parent
 * @param {object} current
 * @param {object} parent
 * @returns {Function}
 */
function removeCurrentFromParent(current, parent) {
  return function () {
    return edit.removeFromParentList({el: current.component, ref: current.ref, parentField: parent.field, parentRef: parent.ref});
  };
}

/**
 * focus on the previous component's field
 * @param {object} parent
 * @param  {object} prev
 * @param {number} textLength
 * @returns {Function}
 */
function focusPreviousComponent(parent, prev, textLength) {
  return function () {
    var newEl = dom.find(parent.component, '[' + references.referenceAttribute + '="' + prev.ref + '"]');

    return focus.focus(newEl, { ref: prev.ref, path: prev.field }).then(function (prevField) {
      // set caret right before the new text we added
      select(prevField, { start: prevField.textContent.length - textLength });
    });
  };
}

/**
 * remove current component, append text to previous component (of the same name)
 * @param {Element} el
 * @returns {Promise|undefined}
 */
function removeComponent(el) {
  var current = getCurrent(el),
    parent = getParent(current.component),
    textLength = el.innerText.length;

  // find the previous component, if any
  return getPrev(current, parent).then(function (prev) {
    if (prev) {
      // there's a previous component with the same name!
      // get the contents of the current field, and append them to the previous component
      return appendToPrev(getFieldContents(el), prev)
        .then(removeCurrentFromParent(current, parent))
        .then(render.reloadComponent.bind(null, prev.ref))
        .then(focusPreviousComponent(parent, prev, textLength));
    }
  });
}

/**
 * add component after current component
 * @param {Element} el
 * @param {string} [text]
 * @returns {Promise}
 */
function addComponent(el, text) {
  var current = getCurrent(el),
    parent = getParent(current.component),
    newData = {};

  // if we're passing data in, set it into the object
  if (text) {
    newData[current.field] = text;
  }

  return edit.createComponent(current.name, newData)
    .then(function (res) {
      var newRef = res._ref;

      return edit.addToParentList({ref: newRef, prevRef: current.ref, parentField: parent.field, parentRef: parent.ref})
        .then(function (newEl) {
          dom.insertAfter(current.component, newEl);
          return render.addComponentsHandlers(newEl)
            .then(function () {
              // focus on the same field in the new component
              focus.focus(newEl, { ref: newRef, path: current.field }).then(function () {
                return newRef;
              });
            });
        });
    });
}

/**
 * split text in a component, creating a new component
 * @param {Element} el
 * @param {object} caret
 * @param {object} observer used to update the current component
 * @returns {Promise}
 */
function splitComponent(el, caret, observer) {
  var textmodel = model.fromElement(dom.create(el.innerHTML.replace(/ /g, ' '))),
    // note: we're removing any nonbreaking spaces BEFORE parsing the text
    splitText = model.split(textmodel, caret.start),
    oldText = splitText[0],
    newText = splitText[1],
    oldFragment = model.toElement(oldText),
    newFragment = model.toElement(newText),
    throwawayDiv = document.createElement('div');

  // to get the innerHTML of the document fragment,
  // we first need to append it to a throwaway div.
  // this is not awesome, but is the "best practice"
  throwawayDiv.appendChild(newFragment);

  // now that we have the split elements, put the old one back in and then create the new one
  dom.clearChildren(el); // clear the current children
  el.appendChild(oldFragment); // add the cleaned dom fragment
  observer.setValue(el.innerHTML); // update the current field
  // this is saved automatically when it's unfocused
  return addComponent(el, throwawayDiv.innerHTML);
}

/**
 * remove current component if we're at the beginning of the field
 * and there's a previous component to append it to
 * @param {Element} el
 * @param {KeyboardEvent} e
 * @returns {undefined|Promise}
 */
function handleComponentDeletion(el, e) {
  var caretPos = select(el);

  if (caretPos.start === 0) {
    e.preventDefault(); // stop page reload
    return removeComponent(el);
  }
}

/**
 * create new component if we're at the end of the field
 * @param {Element} el
 * @param {object} observer
 * @returns {false|Promise}
 */
function handleComponentCreation(el, observer) {
  var caretPos = select(el); // get text after the cursor, if any

  // if there's stuff after the caret, get it
  if (caretPos.start < el.textContent.length - 1) {
    return splitComponent(el, caretPos, observer);
  } else {
    return addComponent(el);
  }
}

function isStyled(styled) {
  return styled ? ' styled' : ''; // note the preeeding space!
}

function addLineBreak() {
  var selection = window.getSelection(),
    range = selection.getRangeAt(0),
    br = document.createElement('br');

  // woah, crazy stuff!
  // this creates a range, inserts the
 tag, then sets the caret after it
  range.deleteContents();
  range.insertNode(br);
  range.setStartAfter(br);
  range.setEndAfter(br);
  range.collapse(false);
  selection.removeAllRanges();
  selection.addRange(range);
}

module.exports = function (result, args) {
  var rivets = result.rivets,
    buttons = args.buttons,
    styled = args.styled,
    enableKeyboardExtras = args.enableKeyboardExtras,
    textInput = getInput(result.el),
    field = dom.create(`<p class="wysiwyg-input${ isStyled(styled) }" data-field="${result.bindings.name}" rv-wysiwyg="data.value"></p>`);

  // if more than 5 buttons, put the rest on the second tier
  if (buttons.length > 5) {
    buttons.splice(5, 0, 'tieredToolbar'); // clicking this expands the toolbar with a second tier
  }

  // put the rich text field after the input
  dom.replaceElement(textInput, field);

  rivets.binders.wysiwyg = {
    publish: true,
    bind: function (el) {
      // this is called when the binder initializes
      var observer = this.observer,
        data = observer.value() || '', // don't print 'undefined' if there's no data
        editor = createEditor(field, buttons),
        italicBtn = dom.find('.medium-editor-action-italic'),
        strikeBtn = dom.find('.medium-editor-action-strikethrough'),
        linkBtn = dom.find('.medium-editor-action-anchor');

      // put the initial data into the editor
      el.innerHTML = data;

      // apply custom styling to buttons
      if (italicBtn) {
        italicBtn.innerHTML = 'I';
      }
      if (strikeBtn) {
        strikeBtn.innerHTML = '<s>M</s>';
      }
      if (linkBtn) {
        linkBtn.innerHTML = `<img src="/media/components/byline-editor/link-icon.svg" />`;
      }

      // hide the tier2 buttons when closing the toolbar
      editor.subscribe('hideToolbar', function () {
        dom.find('.medium-editor-toolbar').classList.remove('show-all');
        dom.find('.medium-editor-toolbar').classList.remove('show-none');
      });

      // persist editor data to data model on input
      editor.subscribe('editableInput', function (e, editable) {
        observer.setValue(editable.innerHTML);
      });

      // persist editor data to data model on paste
      editor.subscribe('editablePaste', function (e, editable) {
        var textmodel = model.fromElement(dom.create(editable.innerHTML)),
          fragment = model.toElement(textmodel);

        // note: we're using the model-text service to clean up and standardize html
        // this is in addition to the cleanup that medium-editor does by default,
        // as well as the options we specified in the cleanTags / cleanReplacements paste config

        dom.clearChildren(editable); // clear the current children
        editable.appendChild(fragment); // add the cleaned dom fragment
        observer.setValue(editable.innerHTML);
      });

      editor.subscribe('editableKeydownDelete', function (e, editable) {
        if (enableKeyboardExtras) {
          handleComponentDeletion(editable, e);
        }
      });

      editor.subscribe('editableKeydownEnter', function (e, editable) {
        if (enableKeyboardExtras && e.shiftKey) {
          // shift+enter was pressed. add a line break
          addLineBreak();
        } else if (enableKeyboardExtras) {
          // enter was pressed. create a new component if certain conditions are met
          handleComponentCreation(editable, observer);
        } else {
          // close the form?
          focus.unfocus();
        }
      });
    }
  };

  return result;
};
<|endoftext|>


<commit_before>// FIXME add minZoom support

goog.provide('ol.source.XYZ');
goog.provide('ol.source.XYZOptions');

goog.require('goog.math');
goog.require('ol.Attribution');
goog.require('ol.Coordinate');
goog.require('ol.Projection');
goog.require('ol.Size');
goog.require('ol.TileCoord');
goog.require('ol.TileUrlFunction');
goog.require('ol.TileUrlFunctionType');
goog.require('ol.source.TileSource');
goog.require('ol.tilegrid.XYZ');

/**
 * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
 *            crossOrigin: (string|undefined),
 *            extent: (ol.Extent|undefined),
 *            maxZoom: number,
 *            tileUrlFunction: (ol.TileUrlFunctionType|undefined),
 *            url: (string|undefined),
 *            urls: (Array.<string>|undefined)}}
 */
ol.source.XYZOptions;

/**
 * @constructor
 * @extends {ol.source.TileSource}
 * @param {ol.source.XYZOptions} xyzOptions XYZ options.
 */
ol.source.XYZ = function(xyzOptions) {

  /**
   * @type {ol.TileUrlFunctionType}
   */
  var tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction;
  // FIXME use goog.nullFunction ?
  if (goog.isDef(xyzOptions.tileUrlFunction)) {
    tileUrlFunction = xyzOptions.tileUrlFunction;
  } else if (goog.isDef(xyzOptions.urls)) {
    tileUrlFunction = ol.TileUrlFunction.createFromTemplates(xyzOptions.urls);
  } else if (goog.isDef(xyzOptions.url)) {
    tileUrlFunction = ol.TileUrlFunction.createFromTemplate(xyzOptions.url);
  }

  var tileGrid = new ol.tilegrid.XYZ({
    maxZoom: xyzOptions.maxZoom
  });

  // FIXME factor out common code
  var extent = xyzOptions.extent;
  if (goog.isDefAndNotNull(extent)) {

    tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
        function(tileCoord) {
          if (xyzOptions.maxZoom < tileCoord.z) {
            return null;
          }
          var n = 1 << tileCoord.z;
          var y = -tileCoord.y - 1;
          if (y < 0 || n <= y) {
            return null;
          }
          var x = goog.math.modulo(tileCoord.x, n);
          var tileExtent = tileGrid.getTileCoordExtent(
              new ol.TileCoord(tileCoord.z, x, tileCoord.y));
          // FIXME we shouldn't need a typecast here
          if (!tileExtent.intersects(/** @type {ol.Extent} */ (extent))) {
            return null;
          }
          return new ol.TileCoord(tileCoord.z, x, y);
        },
        tileUrlFunction);

  } else {

    tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
        function(tileCoord) {
          if (xyzOptions.maxZoom < tileCoord.z) {
            return null;
          }
          var n = 1 << tileCoord.z;
          var y = -tileCoord.y - 1;
          if (y < 0 || n <= y) {
            return null;
          } else {
            var x = goog.math.modulo(tileCoord.x, n);
            return new ol.TileCoord(tileCoord.z, x, y);
          }
        },
        tileUrlFunction);
  }

  goog.base(this, {
    attributions: xyzOptions.attributions,
    crossOrigin: xyzOptions.crossOrigin,
    extent: xyzOptions.extent,
    projection: ol.Projection.getFromCode('EPSG:3857'),
    tileGrid: tileGrid,
    tileUrlFunction: tileUrlFunction
  });

};
goog.inherits(ol.source.XYZ, ol.source.TileSource);
<commit_msg>Add projection option to ol.source.XYZ<commit_after>// FIXME add minZoom support

goog.provide('ol.source.XYZ');
goog.provide('ol.source.XYZOptions');

goog.require('goog.math');
goog.require('ol.Attribution');
goog.require('ol.Coordinate');
goog.require('ol.Projection');
goog.require('ol.Size');
goog.require('ol.TileCoord');
goog.require('ol.TileUrlFunction');
goog.require('ol.TileUrlFunctionType');
goog.require('ol.source.TileSource');
goog.require('ol.tilegrid.XYZ');

/**
 * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
 *            crossOrigin: (string|undefined),
 *            extent: (ol.Extent|undefined),
 *            maxZoom: number,
 *            projection: (ol.Projection|undefined),
 *            tileUrlFunction: (ol.TileUrlFunctionType|undefined),
 *            url: (string|undefined),
 *            urls: (Array.<string>|undefined)}}
 */
ol.source.XYZOptions;

/**
 * @constructor
 * @extends {ol.source.TileSource}
 * @param {ol.source.XYZOptions} xyzOptions XYZ options.
 */
ol.source.XYZ = function(xyzOptions) {

  var projection = xyzOptions.projection ||
      ol.Projection.getFromCode('EPSG:3857');

  /**
   * @type {ol.TileUrlFunctionType}
   */
  var tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction;
  // FIXME use goog.nullFunction ?
  if (goog.isDef(xyzOptions.tileUrlFunction)) {
    tileUrlFunction = xyzOptions.tileUrlFunction;
  } else if (goog.isDef(xyzOptions.urls)) {
    tileUrlFunction = ol.TileUrlFunction.createFromTemplates(xyzOptions.urls);
  } else if (goog.isDef(xyzOptions.url)) {
    tileUrlFunction = ol.TileUrlFunction.createFromTemplate(xyzOptions.url);
  }

  var tileGrid = new ol.tilegrid.XYZ({
    maxZoom: xyzOptions.maxZoom
  });

  // FIXME factor out common code
  var extent = xyzOptions.extent;
  if (goog.isDefAndNotNull(extent)) {

    tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
        function(tileCoord) {
          if (xyzOptions.maxZoom < tileCoord.z) {
            return null;
          }
          var n = 1 << tileCoord.z;
          var y = -tileCoord.y - 1;
          if (y < 0 || n <= y) {
            return null;
          }
          var x = goog.math.modulo(tileCoord.x, n);
          var tileExtent = tileGrid.getTileCoordExtent(
              new ol.TileCoord(tileCoord.z, x, tileCoord.y));
          // FIXME we shouldn't need a typecast here
          if (!tileExtent.intersects(/** @type {ol.Extent} */ (extent))) {
            return null;
          }
          return new ol.TileCoord(tileCoord.z, x, y);
        },
        tileUrlFunction);

  } else {

    tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
        function(tileCoord) {
          if (xyzOptions.maxZoom < tileCoord.z) {
            return null;
          }
          var n = 1 << tileCoord.z;
          var y = -tileCoord.y - 1;
          if (y < 0 || n <= y) {
            return null;
          } else {
            var x = goog.math.modulo(tileCoord.x, n);
            return new ol.TileCoord(tileCoord.z, x, y);
          }
        },
        tileUrlFunction);
  }

  goog.base(this, {
    attributions: xyzOptions.attributions,
    crossOrigin: xyzOptions.crossOrigin,
    extent: xyzOptions.extent,
    projection: projection,
    tileGrid: tileGrid,
    tileUrlFunction: tileUrlFunction
  });

};
goog.inherits(ol.source.XYZ, ol.source.TileSource);
<|endoftext|>


<commit_before>var yo = require('yo-yo')
var translate = require('../translate')
var geocode = require('../map').geocode
var autocomplete = require('../map').autocomplete

var input = yo`<input id='address' type="text" placeholder=${translate.message('search-osc')} onclick=${addAutocomplete}/>`

function addAutocomplete () {
  if (!input.classList.contains('autocomplete')) {
    autocomplete(input)
    input.classList.add('autocomplete')
  }
}

input.addEventListener('keyup', (event) => { event.key === 'Enter' ? geocodeAddress() : null }, false)

var el = yo`

  menu
  ${input}
  search

`

function toggleMenu () {
  var $menu = document.getElementById('menu')
  $menu.classList.toggle('active')
}

function geocodeAddress () {
  geocode(input.value)
}

module.exports = el
<commit_msg>Lose focus when geocode address<commit_after>var yo = require('yo-yo')
var translate = require('../translate')
var geocode = require('../map').geocode
var autocomplete = require('../map').autocomplete

var input = yo`<input id='address' type="text" placeholder=${translate.message('search-osc')} onclick=${addAutocomplete}/>`

function addAutocomplete () {
  if (!input.classList.contains('autocomplete')) {
    autocomplete(input)
    input.classList.add('autocomplete')
  }
}

input.addEventListener('keyup', (event) => { event.key === 'Enter' ? geocodeAddress() : null }, false)

var el = yo`

  menu
  ${input}
  search

`

function toggleMenu () {
  var $menu = document.getElementById('menu')
  $menu.classList.toggle('active')
}

function geocodeAddress () {
  geocode(input.value)
  input.blur()
}

module.exports = el
<|endoftext|>


<commit_before>"use strict";

/**
 * Requires chroma.js
 */
var colormaps = function(){
  const svgNS = "http://www.w3.org/2000/svg";

  var tables = {
    default: [[1,1,1],[0.93,0.001,0],[0.63,0.001,0]],
    OrRd: tableFromChroma(chroma.brewer.OrRd),
    PuBu: tableFromChroma(chroma.brewer.PuBu),
    Oranges: tableFromChroma(chroma.brewer.Oranges),
    Dark2: tableFromChroma(chroma.brewer.Dark2),
    grays: [[1,0.01,0],[1,0.01,0],[1,0.01,0]]
  };
  console.log(tables);

  function tableFromChroma(chromaTable){
    var rtab = [];
    var gtab = [];
    var btab = [];

    chromaTable.forEach(function(chromaElt){
      var rgb = chroma(chromaElt).get('rgb');
      rtab.push(rgb[0]/255);
      gtab.push(rgb[1]/255);
      btab.push(rgb[2]/255);
    });

    return [rtab, gtab, btab];
  }

  function createAndAddTransferNode(parent, name, tableValues){
    var node =  document.createElementNS(svgNS, name);
    node.setAttribute("type", "table");
    node.setAttribute("tableValues", tableString(tableValues));
    parent.appendChild(node);
  }
  /**
   * @param xfer - a feComponentTransfer element that will be mutated.
   * @param name - a table name (use
getTableNames
 to retrieve a list)
   */
  function makeTableFilter(xfer, name){
    if(!xfer){
      console.warn("makeTableFilter requires an existing feComponentTransfer element");
      return;
    }
    var table = tables[name];
    if(!table){
      console.warn("unknown table: " + name);
      return;
    }
    //remove all feComponentTransfer children
    while(xfer.firstChild){
      xfer.removeChild(xfer.firstChild);
    }
    createAndAddTransferNode(xfer, "feFuncR", table[0]);
    createAndAddTransferNode(xfer, "feFuncG", table[1]);
    createAndAddTransferNode(xfer, "feFuncB", table[2]);

    return xfer;
  }

  function tableString(table){
    return table.reduce(function(acc, elt){
      return acc + elt + " ";
    }, "");
  }

  function getTableNames(){
    return Object.keys(tables);
  }

  return {
    makeTableFilter: makeTableFilter,
    getTableNames: getTableNames
  };
}();
<commit_msg>remove unneeded trace and add identity filter<commit_after>"use strict";

/**
 * Color function transfer management.
 * Requires chroma.js
 */
var colormaps = function(){
  const svgNS = "http://www.w3.org/2000/svg";

  var tables = {
    default: [[1,1,1],[0.93,0.001,0],[0.63,0.001,0]],
    OrRd: tableFromChroma(chroma.brewer.OrRd),
    PuBu: tableFromChroma(chroma.brewer.PuBu),
    Oranges: tableFromChroma(chroma.brewer.Oranges),
    Dark2: tableFromChroma(chroma.brewer.Dark2),
    grays: [[1,0.01,0],[1,0.01,0],[1,0.01,0]],
    identity: [[0,1],[0,1],[0,1]]
  };

  function tableFromChroma(chromaTable){
    var rtab = [];
    var gtab = [];
    var btab = [];

    chromaTable.forEach(function(chromaElt){
      var rgb = chroma(chromaElt).get('rgb');
      rtab.push(rgb[0]/255);
      gtab.push(rgb[1]/255);
      btab.push(rgb[2]/255);
    });

    return [rtab, gtab, btab];
  }

  function createAndAddTransferNode(parent, name, tableValues){
    var node =  document.createElementNS(svgNS, name);
    node.setAttribute("type", "table");
    node.setAttribute("tableValues", tableString(tableValues));
    parent.appendChild(node);
  }
  /**
   * @param xfer - a feComponentTransfer element that will be mutated.
   * @param name - a table name (use
getTableNames
 to retrieve a list)
   */
  function makeTableFilter(xfer, name){
    if(!xfer){
      console.warn("makeTableFilter requires an existing feComponentTransfer element");
      return;
    }
    var table = tables[name];
    if(!table){
      console.warn("unknown table: " + name);
      return;
    }
    //remove all feComponentTransfer children
    while(xfer.firstChild){
      xfer.removeChild(xfer.firstChild);
    }
    createAndAddTransferNode(xfer, "feFuncR", table[0]);
    createAndAddTransferNode(xfer, "feFuncG", table[1]);
    createAndAddTransferNode(xfer, "feFuncB", table[2]);

    return xfer;
  }

  function tableString(table){
    return table.reduce(function(acc, elt){
      return acc + elt + " ";
    }, "");
  }

  function getTableNames(){
    return Object.keys(tables);
  }

  return {
    makeTableFilter: makeTableFilter,
    getTableNames: getTableNames
  };
}();
<|endoftext|>


<commit_before>#!/usr/bin/env node

var pica = {};

pica.mods = {};
pica.mods.gpio = require('gpio');
pica.mods.cp = require('child_process');
process.on('tts', function(spec) {
  console.error("SAY: %j", spec.tosay);
  var tts = pica.mods.cp.spawn('tts', spec.tosay);
  tts.stdout.pipe(process.stdout);
  tts.stderr.pipe(process.stderr);
});

pica.pin16 = pica.mods.gpio.export(16, {direction:'out', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 16]});
  pica.pin16.set();
}});
pica.pin21 = pica.mods.gpio.export(21, {direction:'in', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 21]});
}});

pica.pin21.on('change', function(val) {
  process.emit('tts', {tosay:['change', 21, val]});
});

<commit_msg>pinning<commit_after>#!/usr/bin/env node

var pica = {};

pica.mods = {};
pica.mods.gpio = require('gpio');
pica.mods.cp = require('child_process');
process.on('tts', function(spec) {
  console.error("SAY: %j", spec.tosay);
  var tts = pica.mods.cp.spawn('tts', spec.tosay);
  tts.stdout.pipe(process.stdout);
  tts.stderr.pipe(process.stderr);
});

pica.pin16 = pica.mods.gpio.export(16, {direction:'out', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 16]});
  pica.pin16.set();
}});
pica.pin21 = pica.mods.gpio.export(21, {direction:'in', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 21]});
}});

pica.pin21.on('change', function(val) {
  process.emit('tts', {tosay:['change', 21, val]});
});

pica.pin17 = pica.mods.gpio.export(17, {direction:'out', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 17]});
  pica.pin17.set();
}});
pica.pin22 = pica.mods.gpio.export(22, {direction:'in', ready:function() {
  process.emit('tts', {tosay:['ready', 'freddy', 22]});
}});

pica.pin22.on('change', function(val) {
  process.emit('tts', {tosay:['change', 22, val]});
});

<|endoftext|>


<commit_before>/* eslint-disable */
import { login, logout } from '../services/authentication';
describe.skip('data search module', () => {
  before(() => {
    login();
  });

  after(() => {
    logout();
  });

  // TODO: Remove skip when typeahead api is up
  describe('user should be to type and see suggestions', () => {
    it('should open the autocomplete panel and select the first dataset option and route the correct address', () => {
      cy.visit('http://localhost:8080/');
      cy.get('input.js-search-input').trigger('focus');
      cy.get('input.js-search-input').type('Park');
      cy.get('input.js-search-input').trigger('change');
      cy.get('.c-autocomplete').should('exist').and('be.visible');
      cy.get('h4').contains('Straatnamen').siblings('ul').children('li').first().children().first()
      .then(($el) => {
        const firstValue = $el[0].innerText;
        cy.get('h4').contains('Straatnamen').siblings('ul').children('li').first().click();
        cy.get('.o-header__title').contains(firstValue).should('exist').and('be.visible');
      });
    });
  });

  describe('user should be to type and submit', () => {
    beforeEach(() => {
      cy.server()
      cy.route('https://acc.api.data.amsterdam.nl/catalogus/api/3/action/*').as('getResults')
      cy.visit('http://localhost:8080');
      cy.get('input.js-search-input').trigger('focus');
    });

    it('should submit the search and give results', () => {
      cy.get('input.js-search-input').type('Park');
      cy.get('.c-search-form').submit();
      cy.wait('@getResults');
      cy.get('.o-list').should('exist').and('be.visible')
    });

    it('should submit the search and give no results', () => {
      cy.get('input.js-search-input').type('NORESULTS');
      cy.get('.c-search-form').submit();
      cy.wait('@getResults');
      cy.get('.o-list').should('have.length', 0);
    });
  });
});
<commit_msg>Remove comment<commit_after>/* eslint-disable */
import { login, logout } from '../services/authentication';
describe.skip('data search module', () => {
  before(() => {
    login();
  });

  after(() => {
    logout();
  });

  describe('user should be to type and see suggestions', () => {
    it('should open the autocomplete panel and select the first dataset option and route the correct address', () => {
      cy.visit('http://localhost:8080/');
      cy.get('input.js-search-input').trigger('focus');
      cy.get('input.js-search-input').type('Park');
      cy.get('input.js-search-input').trigger('change');
      cy.get('.c-autocomplete').should('exist').and('be.visible');
      cy.get('h4').contains('Straatnamen').siblings('ul').children('li').first().children().first()
      .then(($el) => {
        const firstValue = $el[0].innerText;
        cy.get('h4').contains('Straatnamen').siblings('ul').children('li').first().click();
        cy.get('.o-header__title').contains(firstValue).should('exist').and('be.visible');
      });
    });
  });

  describe('user should be to type and submit', () => {
    beforeEach(() => {
      cy.server()
      cy.route('https://acc.api.data.amsterdam.nl/catalogus/api/3/action/*').as('getResults')
      cy.visit('http://localhost:8080');
      cy.get('input.js-search-input').trigger('focus');
    });

    it('should submit the search and give results', () => {
      cy.get('input.js-search-input').type('Park');
      cy.get('.c-search-form').submit();
      cy.wait('@getResults');
      cy.get('.o-list').should('exist').and('be.visible')
    });

    it('should submit the search and give no results', () => {
      cy.get('input.js-search-input').type('NORESULTS');
      cy.get('.c-search-form').submit();
      cy.wait('@getResults');
      cy.get('.o-list').should('have.length', 0);
    });
  });
});
<|endoftext|>


<commit_before>//config
var host = 'http://localhost:9000'
var ledgerPosition = 0

//init dbs
var seenMethodIdMap = {}
var methodNameMap = {}

$(document).ready(function () {
  if (monitorMode !== 'playback') {
      loadLedgerUpdates(true)
  } else {
      ledgerLoaded()
  }
})

function loadLedgerUpdates () {
  loadLedgerUpdates(false)
}

function loadLedgerUpdates (initialLoad) {
  if (ledgerPosition === 0 && monitorMode === 'live') {
    //get current position, start there; live mode
    $.getJSON(host + '/config', function (result) {
      console.log('Getting current ledger position to begin there for live mode!')
      var journalEntrySize = result.journalEntrySize
      ledgerPosition = (result.ledgerFileSize / result.journalEntrySize)
      console.log('Starting with ledger position: ' + ledgerPosition)

      sendLedgerRequest(initialLoad)
    }).always(function (result) {
      //todo: anything?
    })
  } else {
    //start from beginning; playback mode
    sendLedgerRequest(initialLoad)
  }
}

function sendLedgerRequest (initialLoad) {
  console.log('Checking for ledger updates...')
  var ledgerUpdated = false
  $.getJSON(host + '/ledger/?current_ledger_size=' + ledgerPosition, function (result) {
    ledgerUpdated = true
    console.log('Got ledger updates! Size: ' + result.journalEntryList.length)
    console.log("Ledger difference: " + (result.maximumLedgerPosition - (ledgerPosition + result.journalEntryList.length)))
    $.each(result.journalEntryList, function (i, entry) {
      ledgerPosition++
      addRecordedEvent(entry)

      if (entry.eventMethodId) {
        seenMethodIdMap[entry.eventMethodId] = true
      }
    })
  }).always(function (result) {
    if (!ledgerUpdated && !initialLoad) {
      console.log('No ledger updates found! Current position: ' + ledgerPosition)
    } else if (initialLoad) {
      console.log('Ledger loaded!')
      ledgerLoaded()
    }

    if (!initialLoad && ledgerUpdated) {
      ledgerUpdated0()
    }
  })
}

function ledgerUpdated0 () {
  ledgerUpdated()
}<commit_msg>fixed issue with real-time mode not loading updates when dashboard is started before application it's monitoring<commit_after>//config
var host = 'http://localhost:9000'
var ledgerPosition = 0

//init dbs
var seenMethodIdMap = {}
var methodNameMap = {}

$(document).ready(function () {
  if (monitorMode !== 'playback') {
      loadLedgerUpdates(true)
  } else {
      ledgerLoaded()
  }
})

function loadLedgerUpdates () {
  loadLedgerUpdates(false)
}

function loadLedgerUpdates (initialLoad) {
  if (ledgerPosition === 0 && monitorMode === 'live') {
    //get current position, start there; live mode
    $.getJSON(host + '/config', function (result) {
      console.log('Getting current ledger position to begin there for live mode!')
      var journalEntrySize = result.journalEntrySize
      ledgerPosition = (result.ledgerFileSize / result.journalEntrySize)
      console.log('Starting with ledger position: ' + ledgerPosition)

      sendLedgerRequest(initialLoad)
    }).fail(function (error) {
        //start with 0 position
        sendLedgerRequest(initialLoad)
    }).always(function (result) {
      //todo: anything?
    })
  } else {
    //start from beginning; playback mode
    sendLedgerRequest(initialLoad)
  }
}

function sendLedgerRequest (initialLoad) {
  console.log('Checking for ledger updates...')
  var ledgerUpdated = false
  $.getJSON(host + '/ledger/?current_ledger_size=' + ledgerPosition, function (result) {
    ledgerUpdated = true
    console.log('Got ledger updates! Size: ' + result.journalEntryList.length)
    console.log("Ledger difference: " + (result.maximumLedgerPosition - (ledgerPosition + result.journalEntryList.length)))
    $.each(result.journalEntryList, function (i, entry) {
      ledgerPosition++
      addRecordedEvent(entry)

      if (entry.eventMethodId) {
        seenMethodIdMap[entry.eventMethodId] = true
      }
    })
  }).always(function (result) {
    if (!ledgerUpdated && !initialLoad) {
      console.log('No ledger updates found! Current position: ' + ledgerPosition)
    } else if (initialLoad) {
      console.log('Ledger loaded!')
      ledgerLoaded()
    }

    if (!initialLoad && ledgerUpdated) {
      ledgerUpdated0()
    }
  })
}

function ledgerUpdated0 () {
  ledgerUpdated()
}<|endoftext|>


<commit_before>import React from 'react';
import {StateNavigator} from 'navigation';
import {MobileHistoryManager} from 'navigation-react-mobile';
import Home from './Home';
import Tweet from './Tweet';
import Timeline from './Timeline';
import {getHome, getTweet, getTimeline} from './data';

export default () => {
  const stateNavigator = new StateNavigator([
    {key: 'home', route: ''},
    {key: 'tweet', trackCrumbTrail: true},
    {key: 'timeline', trackCrumbTrail: true}
  ], new MobileHistoryManager(url => {
    var {state, data} = stateNavigator.parseLink(url);
    return stateNavigator.fluent()
      .navigate('home')
      .navigate(state.key, data).url;
  }));

  const {home, tweet, timeline} = stateNavigator.states;
  home.renderScene = () => <Home tweets={getHome()} />;
  tweet.renderScene = ({id}) => <Tweet tweet={getTweet(id)} />;
  timeline.renderScene = ({id}) => <Timeline timeline={getTimeline(id)} />;

  return stateNavigator;
}
<commit_msg>Added photo state<commit_after>import React from 'react';
import {StateNavigator} from 'navigation';
import {MobileHistoryManager} from 'navigation-react-mobile';
import Home from './Home';
import Tweet from './Tweet';
import Timeline from './Timeline';
import {getHome, getTweet, getTimeline} from './data';

export default () => {
  const stateNavigator = new StateNavigator([
    {key: 'home', route: ''},
    {key: 'tweet', trackCrumbTrail: true},
    {key: 'timeline', trackCrumbTrail: true},
    {key: 'photo', trackCrumbTrail: true}
  ], new MobileHistoryManager(url => {
    var {state, data} = stateNavigator.parseLink(url);
    return stateNavigator.fluent()
      .navigate('home')
      .navigate(state.key, data).url;
  }));

  const {home, tweet, timeline, photo} = stateNavigator.states;
  home.renderScene = () => <Home tweets={getHome()} />;
  tweet.renderScene = ({id}) => <Tweet tweet={getTweet(id)} />;
  timeline.renderScene = ({id}) => <Timeline timeline={getTimeline(id)} />;
  photo.renderScene = ({id, index}) => <photo photo={getTweet(id).photos[index]} />;

  return stateNavigator;
}
<|endoftext|>


<commit_before>define('parsley/utils', function () {
  var globalID = 1,
    pastWarnings = {},
    // http://support.microsoft.com/kb/167820
    // http://stackoverflow.com/questions/19999388/jquery-check-if-user-is-using-ie
    msie = (function () {
          var
            ua = window.navigator.userAgent,
            msie = ua.indexOf('MSIE ');

          if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./))
            return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);

          return 0;
       })();

  var ParsleyUtils = {
    // Parsley DOM-API
    // returns object from dom attributes and values
    attr: function ($element, namespace, obj) {
      var
        attribute, attributes,
        regex = new RegExp('^' + namespace, 'i');

      if ('undefined' === typeof obj)
        obj = {};
      else {
        // Clear all own properties. This won't affect prototype's values
        for (var i in obj) {
          if (obj.hasOwnProperty(i))
            delete obj[i];
        }
      }

      if ('undefined' === typeof $element || 'undefined' === typeof $element[0])
        return obj;

      attributes = $element[0].attributes;
      for (var i = attributes.length; i--; ) {
        attribute = attributes[i];

        if ('undefined' !== typeof attribute && null !== attribute && (!msie || msie >= 8 || attribute.specified) && regex.test(attribute.name)) {
          obj[this.camelize(attribute.name.slice(namespace.length))] = this.deserializeValue(attribute.value);
        }
      }

      return obj;
    },

    checkAttr: function ($element, namespace, checkAttr) {
      return $element.is('[' + namespace + checkAttr + ']');
    },

    setAttr: function ($element, namespace, attr, value) {
      $element[0].setAttribute(this.dasherize(namespace + attr), String(value));
    },

    generateID: function () {
      return '' + globalID++;
    },

    /** Third party functions **/
    // Zepto deserialize function
    deserializeValue: function (value) {
      var num;

      try {
        return value ?
          value == "true" ||
          (value == "false" ? false :
          value == "null" ? null :
          !isNaN(num = Number(value)) ? num :
          /^[\[\{]/.test(value) ? $.parseJSON(value) :
          value)
          : value;
      } catch (e) { return value; }
    },

    // Zepto camelize function
    camelize: function (str) {
      return str.replace(/-+(.)?/g, function (match, chr) {
        return chr ? chr.toUpperCase() : '';
      });
    },

    // Zepto dasherize function
    dasherize: function (str) {
      return str.replace(/::/g, '/')
        .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
        .replace(/([a-z\d])([A-Z])/g, '$1_$2')
        .replace(/_/g, '-')
        .toLowerCase();
    },

    warn: function() {
      if (window.console && window.console.warn)
        window.console.warn.apply(window.console, arguments);
    },

    warnOnce: function(msg) {
      if (!pastWarnings[msg]) {
        pastWarnings[msg] = true;
        this.warn.apply(this, arguments);
      }
    },

    // Object.create polyfill, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill
    objectCreate: Object.create || (function () {
      var Object = function () {};
      return function (prototype) {
        if (arguments.length > 1) {
          throw Error('Second argument not supported');
        }
        if (typeof prototype != 'object') {
          throw TypeError('Argument must be an object');
        }
        Object.prototype = prototype;
        var result = new Object();
        Object.prototype = null;
        return result;
      };
    })()
  };

  return ParsleyUtils;
});
<commit_msg>Simplify [#855]<commit_after>define('parsley/utils', function () {
  var globalID = 1,
    pastWarnings = {},
    // http://support.microsoft.com/kb/167820
    // http://stackoverflow.com/questions/19999388/jquery-check-if-user-is-using-ie
    msie = (function () {
          var
            ua = window.navigator.userAgent,
            msie = ua.indexOf('MSIE ');

          if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./))
            return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);

          return 0;
       })();

  var ParsleyUtils = {
    // Parsley DOM-API
    // returns object from dom attributes and values
    attr: function ($element, namespace, obj) {
      var
        attribute, attributes,
        regex = new RegExp('^' + namespace, 'i');

      if ('undefined' === typeof obj)
        obj = {};
      else {
        // Clear all own properties. This won't affect prototype's values
        for (var i in obj) {
          if (obj.hasOwnProperty(i))
            delete obj[i];
        }
      }

      if ('undefined' === typeof $element || 'undefined' === typeof $element[0])
        return obj;

      attributes = $element[0].attributes;
      for (var i = attributes.length; i--; ) {
        attribute = attributes[i];

        if (attribute && (!msie || msie >= 8 || attribute.specified) && regex.test(attribute.name)) {
          obj[this.camelize(attribute.name.slice(namespace.length))] = this.deserializeValue(attribute.value);
        }
      }

      return obj;
    },

    checkAttr: function ($element, namespace, checkAttr) {
      return $element.is('[' + namespace + checkAttr + ']');
    },

    setAttr: function ($element, namespace, attr, value) {
      $element[0].setAttribute(this.dasherize(namespace + attr), String(value));
    },

    generateID: function () {
      return '' + globalID++;
    },

    /** Third party functions **/
    // Zepto deserialize function
    deserializeValue: function (value) {
      var num;

      try {
        return value ?
          value == "true" ||
          (value == "false" ? false :
          value == "null" ? null :
          !isNaN(num = Number(value)) ? num :
          /^[\[\{]/.test(value) ? $.parseJSON(value) :
          value)
          : value;
      } catch (e) { return value; }
    },

    // Zepto camelize function
    camelize: function (str) {
      return str.replace(/-+(.)?/g, function (match, chr) {
        return chr ? chr.toUpperCase() : '';
      });
    },

    // Zepto dasherize function
    dasherize: function (str) {
      return str.replace(/::/g, '/')
        .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
        .replace(/([a-z\d])([A-Z])/g, '$1_$2')
        .replace(/_/g, '-')
        .toLowerCase();
    },

    warn: function() {
      if (window.console && window.console.warn)
        window.console.warn.apply(window.console, arguments);
    },

    warnOnce: function(msg) {
      if (!pastWarnings[msg]) {
        pastWarnings[msg] = true;
        this.warn.apply(this, arguments);
      }
    },

    // Object.create polyfill, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill
    objectCreate: Object.create || (function () {
      var Object = function () {};
      return function (prototype) {
        if (arguments.length > 1) {
          throw Error('Second argument not supported');
        }
        if (typeof prototype != 'object') {
          throw TypeError('Argument must be an object');
        }
        Object.prototype = prototype;
        var result = new Object();
        Object.prototype = null;
        return result;
      };
    })()
  };

  return ParsleyUtils;
});
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name PathItem
 *
 * @class The PathItem class is the base for any items that describe paths
 * and offer standardised methods for drawing and path manipulation, such as
 * {@link Path} and {@link CompoundPath}.
 *
 * @extends Item
 */
var PathItem = this.PathItem = Item.extend(/** @lends PathItem# */{
	// Note: The PathItem class is currently empty but holds the documentation
	// for all the methods that exist both on Path and CompoundPath.
	/**
	 * Smooth bezier curves without changing the amount of segments or their
	 * points, by only smoothing and adjusting their handle points, for both
	 * open ended and closed paths.
	 *
	 * @name PathItem#smooth
	 * @function
	 *
	 * @example {@paperscript}
	 * // Smoothing a closed shape:
	 *
	 * // Create a rectangular path with its top-left point at
	 * // {x: 30, y: 25} and a size of {width: 50, height: 50}:
	 * var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
	 * path.strokeColor = 'black';
	 *
	 * // Select the path, so we can see its handles:
	 * path.fullySelected = true;
	 *
	 * // Create a copy of the path and move it 100pt to the right:
	 * var copy = path.clone();
	 * copy.position.x += 100;
	 *
	 * // Smooth the segments of the copy:
	 * copy.smooth();
	 *
	 * @example {@paperscript height=220}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * path.add(new Point(30, 50));
	 *
	 * var y = 5;
	 * var x = 3;
	 *
	 * for (var i = 0; i < 28; i++) {
	 *     y *= -1.1;
	 *     x *= 1.1;
	 *     path.lineBy(x, y);
	 * }
	 *
	 * // Create a copy of the path and move it 100pt down:
	 * var copy = path.clone();
	 * copy.position.y += 120;
	 *
	 * // Set its stroke color to red:
	 * copy.strokeColor = 'red';
	 *
	 * // Smooth the segments of the copy:
	 * copy.smooth();
	 */

	/**
	 * {@grouptitle Postscript Style Drawing Commands}
	 *
	 * If called on a {@link CompoundPath}, a new {@link Path} is created as a
	 * child and the point is added as its first segment. On a normal empty
	 * {@link Path}, the point is simply added as its first segment.
	 *
	 * @name PathItem#moveTo
	 * @function
	 * @param {Point} point
	 */

	// DOCS: Document #lineTo()
	/**
	 * @name PathItem#lineTo
	 * @function
	 * @param {Point} point
	 */

	/**
	 * Adds a cubic bezier curve to the path, defined by two handles and a to
	 * point.
	 *
	 * @name PathItem#cubicCurveTo
	 * @function
	 * @param {Point} handle1
	 * @param {Point} handle2
	 * @param {Point} to
	 */

	/**
	 * Adds a quadratic bezier curve to the path, defined by a handle and a to
	 * point.
	 *
	 * @name PathItem#quadraticCurveTo
	 * @function
	 * @param {Point} handle
	 * @param {Point} to
	 */

	// DOCS: Document PathItem#curveTo() 'paramater' param.
	/**
	 * Draws a curve from the position of the last segment point in the path
	 * that goes through the specified {@code through} point, to the specified
	 * {@code to} point by adding one segment to the path.
	 *
	 * @name PathItem#curveTo
	 * @function
	 * @param {Point} through the point through which the curve should go
	 * @param {Point} to the point where the curve should end
	 * @param {Number} [parameter=0.5]
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 *
	 * var myPath;
	 * function onMouseDrag(event) {
	 * 	// If we created a path before, remove it:
	 * 	if (myPath) {
	 * 	    myPath.remove();
	 * 	}
	 *
	 * 	// Create a new path and add a segment point to it
	 * 	// at {x: 150, y: 150):
	 * 	myPath = new Path();
	 * 	myPath.add(150, 150);
	 *
	 * 	// Draw a curve through the position of the mouse to 'toPoint'
	 * 	var toPoint = new Point(350, 150);
	 * 	myPath.curveTo(event.point, toPoint);
	 *
	 * 	// Select the path, so we can see its segments:
	 * 	myPath.selected = true;
	 * }
     *
	 * // When the mouse is released, deselect the path
	 * // and set its stroke-color to black:
	 * function onMouseUp(event) {
	 * 	myPath.selected = false;
	 * 	myPath.strokeColor = 'black';
	 * }
	 */

	/**
	 * Draws an arc from the position of the last segment point in the path that
	 * goes through the specified {@code through} point, to the specified
	 * {@code to} point by adding one or more segments to the path.
	 *
	 * @name PathItem#arcTo
	 * @function
	 * @param {Point} through the point where the arc should pass through
	 * @param {Point} to the point where the arc should end
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * var firstPoint = new Point(30, 75);
	 * path.add(firstPoint);
	 *
	 * // The point through which we will create the arc:
	 * var throughPoint = new Point(40, 40);
	 *
	 * // The point at which the arc will end:
	 * var toPoint = new Point(130, 75);
	 *
	 * // Draw an arc through 'throughPoint' to 'toPoint'
	 * path.arcTo(throughPoint, toPoint);
	 *
	 * // Add a red circle shaped path at the position of 'throughPoint':
	 * var circle = new Path.Circle(throughPoint, 3);
	 * circle.fillColor = 'red';
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 *
	 * var myPath;
	 * function onMouseDrag(event) {
	 * 	// If we created a path before, remove it:
	 * 	if (myPath) {
	 * 	    myPath.remove();
	 * 	}
	 *
	 * 	// Create a new path and add a segment point to it
	 * 	// at {x: 150, y: 150):
	 * 	myPath = new Path();
	 * 	myPath.add(150, 150);
	 *
	 * 	// Draw an arc through the position of the mouse to 'toPoint'
	 * 	var toPoint = new Point(350, 150);
	 * 	myPath.arcTo(event.point, toPoint);
	 *
	 * 	// Select the path, so we can see its segments:
	 * 	myPath.selected = true;
	 * }
	 *
	 * // When the mouse is released, deselect the path
	 * // and fill it with black.
	 * function onMouseUp(event) {
	 * 	myPath.selected = false;
	 * 	myPath.fillColor = 'black';
	 * }
	 */
	/**
	 * Draws an arc from the position of the last segment point in the path to
	 * the specified point by adding one or more segments to the path.
	 *
	 * @name PathItem#arcTo
	 * @function
	 * @param {Point} to the point where the arc should end
	 * @param {Boolean} [clockwise=true] specifies whether the arc should be
	 *        drawn in clockwise direction.
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * path.add(new Point(30, 75));
	 * path.arcTo(new Point(130, 75));
	 *
	 * var path2 = new Path();
	 * path2.strokeColor = 'red';
	 * path2.add(new Point(180, 25));
	 *
	 * // To draw an arc in anticlockwise direction,
	 * // we pass 'false' as the second argument to arcTo:
	 * path2.arcTo(new Point(280, 25), false);
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 * var myPath;
	 *
	 * // The mouse has to move at least 20 points before
	 * // the next mouse drag event is fired:
	 * tool.minDistance = 20;
	 *
	 * // When the user clicks, create a new path and add
	 * // the current mouse position to it as its first segment:
	 * function onMouseDown(event) {
	 * 	myPath = new Path();
	 * 	myPath.strokeColor = 'black';
	 * 	myPath.add(event.point);
	 * }
	 *
	 * // On each mouse drag event, draw an arc to the current
	 * // position of the mouse:
	 * function onMouseDrag(event) {
	 * 	myPath.arcTo(event.point);
	 * }
	 */

	/**
	 * Closes the path. When closed, Paper.js connects the first and last
	 * segments.
	 *
	 * @name PathItem#closePath
	 * @function
	 * @see Path#closed
	 */

	/**
	 * {@grouptitle Relative Drawing Commands}
	 *
	 * If called on a {@link CompoundPath}, a new {@link Path} is created as a
	 * child and the point is added as its first segment relative to the
	 * position of the last segment of the current path.
	 *
	 * @name PathItem#moveBy
	 * @function
	 * @param {Point} point
	 */

	/**
	 * Adds a segment relative to the last segment point of the path.
	 *
	 * @name PathItem#lineBy
	 * @function
	 * @param {Point} vector The vector which is added to the position of the
	 *        last segment of the path, to become the new segment.
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * // Add a segment at {x: 50, y: 50}
	 * path.add(25, 25);
	 *
	 * // Add a segment relative to the last segment of the path.
	 * // 50 in x direction and 0 in y direction, becomes {x: 75, y: 25}
	 * path.lineBy(50, 0);
	 *
	 * // 0 in x direction and 50 in y direction, becomes {x: 75, y: 75}
	 * path.lineBy(0, 50);
	 *
	 * @example {@paperscript height=300}
	 * // Drawing a spiral using lineBy:
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * // Add the first segment at {x: 50, y: 50}
	 * path.add(view.center);
	 *
	 * // Loop 500 times:
	 * for (var i = 0; i < 500; i++) {
	 * 	// Create a vector with an ever increasing length
	 * 	// and an angle in increments of 45 degrees
	 * 	var vector = new Point({
	 * 	    angle: i * 45,
	 * 	    length: i / 2
	 * 	});
	 * 	// Add the vector relatively to the last segment point:
	 * 	path.lineBy(vector);
	 * }
	 *
	 * // Smooth the handles of the path:
	 * path.smooth();
	 *
	 * // Uncomment the following line and click on 'run' to see
	 * // the construction of the path:
	 * // path.selected = true;
	 */

	// DOCS: Document Path#curveBy()
	/**
	 * @name PathItem#curveBy
	 * @function
	 * @param {Point} throughVector
	 * @param {Point} toVector
	 * @param {Number} [parameter=0.5]
	 */

	// DOCS: Document Path#arcBy()
	/**
	 * @name PathItem#arcBy
	 * @function
	 * @param {Point} throughVector
	 * @param {Point} toVector
	 */
});
<commit_msg>Update documentation a bit.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name PathItem
 *
 * @class The PathItem class is the base for any items that describe paths
 * and offer standardised methods for drawing and path manipulation, such as
 * {@link Path} and {@link CompoundPath}.
 *
 * @extends Item
 */
var PathItem = this.PathItem = Item.extend(/** @lends PathItem# */{
	// Note: The PathItem class is currently empty but holds the documentation
	// for all the methods that exist both on Path and CompoundPath.
	/**
	 * Smooth bezier curves without changing the amount of segments or their
	 * points, by only smoothing and adjusting their handle points, for both
	 * open ended and closed paths.
	 *
	 * @name PathItem#smooth
	 * @function
	 *
	 * @example {@paperscript}
	 * // Smoothing a closed shape:
	 *
	 * // Create a rectangular path with its top-left point at
	 * // {x: 30, y: 25} and a size of {width: 50, height: 50}:
	 * var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
	 * path.strokeColor = 'black';
	 *
	 * // Select the path, so we can see its handles:
	 * path.fullySelected = true;
	 *
	 * // Create a copy of the path and move it 100pt to the right:
	 * var copy = path.clone();
	 * copy.position.x += 100;
	 *
	 * // Smooth the segments of the copy:
	 * copy.smooth();
	 *
	 * @example {@paperscript height=220}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * path.add(new Point(30, 50));
	 *
	 * var y = 5;
	 * var x = 3;
	 *
	 * for (var i = 0; i < 28; i++) {
	 *     y *= -1.1;
	 *     x *= 1.1;
	 *     path.lineBy(x, y);
	 * }
	 *
	 * // Create a copy of the path and move it 100pt down:
	 * var copy = path.clone();
	 * copy.position.y += 120;
	 *
	 * // Set its stroke color to red:
	 * copy.strokeColor = 'red';
	 *
	 * // Smooth the segments of the copy:
	 * copy.smooth();
	 */

	/**
	 * {@grouptitle Postscript Style Drawing Commands}
	 *
	 * On a normal empty {@link Path}, the point is simply added as the path's
	 * first segment. If called on a {@link CompoundPath}, a new {@link Path} is
	 * created as a child and the point is added as its first segment.
	 *
	 * @name PathItem#moveTo
	 * @function
	 * @param {Point} point
	 */

	// DOCS: Document #lineTo()
	/**
	 * @name PathItem#lineTo
	 * @function
	 * @param {Point} point
	 */

	/**
	 * Adds a cubic bezier curve to the path, defined by two handles and a to
	 * point.
	 *
	 * @name PathItem#cubicCurveTo
	 * @function
	 * @param {Point} handle1
	 * @param {Point} handle2
	 * @param {Point} to
	 */

	/**
	 * Adds a quadratic bezier curve to the path, defined by a handle and a to
	 * point.
	 *
	 * @name PathItem#quadraticCurveTo
	 * @function
	 * @param {Point} handle
	 * @param {Point} to
	 */

	// DOCS: Document PathItem#curveTo() 'paramater' param.
	/**
	 * Draws a curve from the position of the last segment point in the path
	 * that goes through the specified {@code through} point, to the specified
	 * {@code to} point by adding one segment to the path.
	 *
	 * @name PathItem#curveTo
	 * @function
	 * @param {Point} through the point through which the curve should go
	 * @param {Point} to the point where the curve should end
	 * @param {Number} [parameter=0.5]
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 *
	 * var myPath;
	 * function onMouseDrag(event) {
	 * 	// If we created a path before, remove it:
	 * 	if (myPath) {
	 * 	    myPath.remove();
	 * 	}
	 *
	 * 	// Create a new path and add a segment point to it
	 * 	// at {x: 150, y: 150):
	 * 	myPath = new Path();
	 * 	myPath.add(150, 150);
	 *
	 * 	// Draw a curve through the position of the mouse to 'toPoint'
	 * 	var toPoint = new Point(350, 150);
	 * 	myPath.curveTo(event.point, toPoint);
	 *
	 * 	// Select the path, so we can see its segments:
	 * 	myPath.selected = true;
	 * }
     *
	 * // When the mouse is released, deselect the path
	 * // and set its stroke-color to black:
	 * function onMouseUp(event) {
	 * 	myPath.selected = false;
	 * 	myPath.strokeColor = 'black';
	 * }
	 */

	/**
	 * Draws an arc from the position of the last segment point in the path that
	 * goes through the specified {@code through} point, to the specified
	 * {@code to} point by adding one or more segments to the path.
	 *
	 * @name PathItem#arcTo
	 * @function
	 * @param {Point} through the point where the arc should pass through
	 * @param {Point} to the point where the arc should end
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * var firstPoint = new Point(30, 75);
	 * path.add(firstPoint);
	 *
	 * // The point through which we will create the arc:
	 * var throughPoint = new Point(40, 40);
	 *
	 * // The point at which the arc will end:
	 * var toPoint = new Point(130, 75);
	 *
	 * // Draw an arc through 'throughPoint' to 'toPoint'
	 * path.arcTo(throughPoint, toPoint);
	 *
	 * // Add a red circle shaped path at the position of 'throughPoint':
	 * var circle = new Path.Circle(throughPoint, 3);
	 * circle.fillColor = 'red';
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 *
	 * var myPath;
	 * function onMouseDrag(event) {
	 * 	// If we created a path before, remove it:
	 * 	if (myPath) {
	 * 	    myPath.remove();
	 * 	}
	 *
	 * 	// Create a new path and add a segment point to it
	 * 	// at {x: 150, y: 150):
	 * 	myPath = new Path();
	 * 	myPath.add(150, 150);
	 *
	 * 	// Draw an arc through the position of the mouse to 'toPoint'
	 * 	var toPoint = new Point(350, 150);
	 * 	myPath.arcTo(event.point, toPoint);
	 *
	 * 	// Select the path, so we can see its segments:
	 * 	myPath.selected = true;
	 * }
	 *
	 * // When the mouse is released, deselect the path
	 * // and fill it with black.
	 * function onMouseUp(event) {
	 * 	myPath.selected = false;
	 * 	myPath.fillColor = 'black';
	 * }
	 */
	/**
	 * Draws an arc from the position of the last segment point in the path to
	 * the specified point by adding one or more segments to the path.
	 *
	 * @name PathItem#arcTo
	 * @function
	 * @param {Point} to the point where the arc should end
	 * @param {Boolean} [clockwise=true] specifies whether the arc should be
	 *        drawn in clockwise direction.
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * path.add(new Point(30, 75));
	 * path.arcTo(new Point(130, 75));
	 *
	 * var path2 = new Path();
	 * path2.strokeColor = 'red';
	 * path2.add(new Point(180, 25));
	 *
	 * // To draw an arc in anticlockwise direction,
	 * // we pass 'false' as the second argument to arcTo:
	 * path2.arcTo(new Point(280, 25), false);
	 *
	 * @example {@paperscript height=300}
	 * // Interactive example. Click and drag in the view below:
	 * var myPath;
	 *
	 * // The mouse has to move at least 20 points before
	 * // the next mouse drag event is fired:
	 * tool.minDistance = 20;
	 *
	 * // When the user clicks, create a new path and add
	 * // the current mouse position to it as its first segment:
	 * function onMouseDown(event) {
	 * 	myPath = new Path();
	 * 	myPath.strokeColor = 'black';
	 * 	myPath.add(event.point);
	 * }
	 *
	 * // On each mouse drag event, draw an arc to the current
	 * // position of the mouse:
	 * function onMouseDrag(event) {
	 * 	myPath.arcTo(event.point);
	 * }
	 */

	/**
	 * Closes the path. When closed, Paper.js connects the first and last
	 * segments.
	 *
	 * @name PathItem#closePath
	 * @function
	 * @see Path#closed
	 */

	/**
	 * {@grouptitle Relative Drawing Commands}
	 *
	 * If called on a {@link CompoundPath}, a new {@link Path} is created as a
	 * child and a point is added as its first segment relative to the
	 * position of the last segment of the current path.
	 *
	 * @name PathItem#moveBy
	 * @function
	 * @param {Point} vector
	 */

	/**
	 * Adds a segment relative to the last segment point of the path.
	 *
	 * @name PathItem#lineBy
	 * @function
	 * @param {Point} vector The vector which is added to the position of the
	 *        last segment of the path, to become the new segment.
	 *
	 * @example {@paperscript}
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * // Add a segment at {x: 50, y: 50}
	 * path.add(25, 25);
	 *
	 * // Add a segment relative to the last segment of the path.
	 * // 50 in x direction and 0 in y direction, becomes {x: 75, y: 25}
	 * path.lineBy(50, 0);
	 *
	 * // 0 in x direction and 50 in y direction, becomes {x: 75, y: 75}
	 * path.lineBy(0, 50);
	 *
	 * @example {@paperscript height=300}
	 * // Drawing a spiral using lineBy:
	 * var path = new Path();
	 * path.strokeColor = 'black';
	 *
	 * // Add the first segment at {x: 50, y: 50}
	 * path.add(view.center);
	 *
	 * // Loop 500 times:
	 * for (var i = 0; i < 500; i++) {
	 * 	// Create a vector with an ever increasing length
	 * 	// and an angle in increments of 45 degrees
	 * 	var vector = new Point({
	 * 	    angle: i * 45,
	 * 	    length: i / 2
	 * 	});
	 * 	// Add the vector relatively to the last segment point:
	 * 	path.lineBy(vector);
	 * }
	 *
	 * // Smooth the handles of the path:
	 * path.smooth();
	 *
	 * // Uncomment the following line and click on 'run' to see
	 * // the construction of the path:
	 * // path.selected = true;
	 */

	// DOCS: Document Path#curveBy()
	/**
	 * @name PathItem#curveBy
	 * @function
	 * @param {Point} throughVector
	 * @param {Point} toVector
	 * @param {Number} [parameter=0.5]
	 */

	// DOCS: Document Path#arcBy()
	/**
	 * @name PathItem#arcBy
	 * @function
	 * @param {Point} throughVector
	 * @param {Point} toVector
	 */
});
<|endoftext|>