<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/edit/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'),
  site = require('../services/site'),
  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, '']
      ]
    },
    anchor: {
      linkValidation: true // check for common protocols on links
    },
    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 db.get(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 edit.getData(prev.ref).then(function (prevData) {
    var prevFieldData = _.get(prevData, prev.field),
      prevFieldHTML = prevFieldData.value,
      throwawayDiv = document.createElement('div'),
      textmodel, fragment;

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

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

    return edit.savePartial(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.textContent.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 && caretPos.end === 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 name = result.name,
    binders = result.binders,
    buttons = args.buttons,
    styled = args.styled,
    enableKeyboardExtras = args.enableKeyboardExtras,
    textInput = getInput(result.el),
    field = dom.create(`<p class="wysiwyg-input${ isStyled(styled) }" data-field="${name}" rv-wysiwyg="${name}.data.value" data-wysiwyg-buttons="${buttons.join(',')}"></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);

  /**
   * match extension names when instantiating medium-editor
   * @param {string} extname e.g. 'italic'
   * @returns {Function}
   */
  function findExtension(extname) {
    return function (ext) {
      return ext.name === extname;
    };
  }

  binders.wysiwyg = {
    publish: true,
    block: false,
    bind: function (el) {
      // this is called when the binder initializes
      var toolbarButtons = el.getAttribute('data-wysiwyg-buttons').split(','),
        observer = this.observer,
        data = observer.value() || '', // don't print 'undefined' if there's no data
        editor = createEditor(el, toolbarButtons),
        italicExtension = _.find(editor.extensions, findExtension('italic')),
        strikethoughExtension = _.find(editor.extensions, findExtension('strikethrough')),
        linkExtension = _.find(editor.extensions, findExtension('anchor'));

      // apply custom styling to buttons
      if (italicExtension) {
        italicExtension.button.innerHTML = 'I';
      }
      if (strikethoughExtension) {
        strikethoughExtension.button.innerHTML = '<s>M</s>';
      }
      if (linkExtension) {
        linkExtension.button.innerHTML = `<img src="${site.get('assetPath')}/media/components/clay-kiln/link-icon.svg" />`;
      }

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

      // hide the tier2 buttons when closing the toolbar
      editor.subscribe('hideToolbar', function onHideToolbar() {
        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 onEditableInput(e, editable) {
        if (editable) {
          // editable exists when you're typing into the field
          observer.setValue(editable.innerHTML);
        } else if (e.target) {
          // editable doesn't exist (but e.target does) when hitting enter after entering in a link
          observer.setValue(e.target.innerHTML);
        }
      });

      // persist editor data to data model on paste
      editor.subscribe('editablePaste', function onEditablePaste(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 onEditableKeydownDelete(e, editable) {
        if (enableKeyboardExtras) {
          handleComponentDeletion(editable, e);
        }
      });

      editor.subscribe('editableKeydownEnter', function onEditableKeydownEnter(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>moved wysiwyg into separate function<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/edit/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'),
  site = require('../services/site'),
  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, '']
      ]
    },
    anchor: {
      linkValidation: true // check for common protocols on links
    },
    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 db.get(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 edit.getData(prev.ref).then(function (prevData) {
    var prevFieldData = _.get(prevData, prev.field),
      prevFieldHTML = prevFieldData.value,
      throwawayDiv = document.createElement('div'),
      textmodel, fragment;

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

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

    return edit.savePartial(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.textContent.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 && caretPos.end === 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);
}

/**
 * match extension names when instantiating medium-editor
 * @param {string} extname e.g. 'italic'
 * @returns {Function}
 */
function findExtension(extname) {
  return function (ext) {
    return ext.name === extname;
  };
}

function initWysiwygBinder(enableKeyboardExtras) {
  return {
    publish: true,
    bind: function (el) {
      // this is called when the binder initializes
      var toolbarButtons = el.getAttribute('data-wysiwyg-buttons').split(','),
        observer = this.observer,
        data = observer.value() || '', // don't print 'undefined' if there's no data
        editor = createEditor(el, toolbarButtons),
        italicExtension = _.find(editor.extensions, findExtension('italic')),
        strikethoughExtension = _.find(editor.extensions, findExtension('strikethrough')),
        linkExtension = _.find(editor.extensions, findExtension('anchor'));

      // apply custom styling to buttons
      if (italicExtension) {
        italicExtension.button.innerHTML = 'I';
      }
      if (strikethoughExtension) {
        strikethoughExtension.button.innerHTML = '<s>M</s>';
      }
      if (linkExtension) {
        linkExtension.button.innerHTML = `<img src="${site.get('assetPath')}/media/components/clay-kiln/link-icon.svg" />`;
      }

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

      // hide the tier2 buttons when closing the toolbar
      editor.subscribe('hideToolbar', function onHideToolbar() {
        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 onEditableInput(e, editable) {
        if (editable) {
          // editable exists when you're typing into the field
          observer.setValue(editable.innerHTML);
        } else if (e.target) {
          // editable doesn't exist (but e.target does) when hitting enter after entering in a link
          observer.setValue(e.target.innerHTML);
        }
      });

      // persist editor data to data model on paste
      editor.subscribe('editablePaste', function onEditablePaste(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 onEditableKeydownDelete(e, editable) {
        if (enableKeyboardExtras) {
          handleComponentDeletion(editable, e);
        }
      });

      editor.subscribe('editableKeydownEnter', function onEditableKeydownEnter(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();
        }
      });
    }
  };
}

module.exports = function (result, args) {
  var name = result.name,
    binders = result.binders,
    buttons = args.buttons,
    styled = args.styled,
    enableKeyboardExtras = args.enableKeyboardExtras,
    textInput = getInput(result.el),
    field = dom.create(`<p class="wysiwyg-input${ isStyled(styled) }" data-field="${name}" rv-wysiwyg="${name}.data.value" data-wysiwyg-buttons="${buttons.join(',')}"></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);

  // add the binder
  binders.wysiwyg = initWysiwygBinder(enableKeyboardExtras);

  return result;
};
<|endoftext|>


<commit_before>class SkinnyProgressBar {
    constructor(options){
        this.options = options || {};

        this.options.el = this.options.el ? this.options.el : "body";
        this.options.color = this.options.color ? this.options.color : "#01579B";

        this.model = {
            el: this.options.el,
            value: 0,
            options: this.options
        };

        this.progressBar = $("

");
        this.progressBar.css({
            "z-index": "99999",
            "height": "3px",
            "width": "0",
            "background-color": this.options.color,
            "opacity": "1",
            "border-top-right-radius": "4px",
            "border-bottom-right-radius": "4px",
            "transition": "width 200ms, opacity 400ms"
        });

        if (this.model.el == "body")
            $(this.model.el).prepend(this.progressBar);
        else
            $(this.model.el).append(this.progressBar);
    }

    load(value) {
        this.model.value = value;
        this.progressBar.css("width", this.model.value + "%");

        if (this.model.value == 100)
            this.reset();
    }

    reset() {
        this.model.value = 0;

        var self = this;
        setTimeout(function(){
            self.progressBar.css("opacity", 0);
        }, 200);

        setTimeout(function(){
            self.progressBar.css("opacity", 1);
        }, 700);
    }
}
<commit_msg>fixing reset()<commit_after>'use strict';

class SkinnyProgressBar {
    constructor(options){
        this.options = options || {};

        this.options.el = this.options.el ? this.options.el : "body";
        this.options.color = this.options.color ? this.options.color : "#01579B";

        this.model = {
            el: this.options.el,
            value: 0,
            options: this.options
        };

        this.progressBar = $("

");
        this.progressBar.css({
            "z-index": "99999",
            "height": "3px",
            "width": "0",
            "background-color": this.options.color,
            "opacity": "1",
            "border-top-right-radius": "4px",
            "border-bottom-right-radius": "4px",
            "transition": "width 200ms, opacity 500ms"
        });

        if (this.model.el == "body")
            $(this.model.el).prepend(this.progressBar);
        else
            $(this.model.el).append(this.progressBar);
    }

    load(value) {
        this.model.value = value;
        this.progressBar.css("width", this.model.value + "%");

        if (this.model.value == 100)
            this.reset();
    }

    reset() {
        this.model.value = 0;

        var self = this;
        setTimeout(() => {
            self.progressBar.css("opacity", 0);
            setTimeout(() => {
                self.progressBar.css("width", self.model.value + "%");
                setTimeout(() => {
                    self.progressBar.css("opacity", 1);
                }, 200);
            }, 500);
        }, 200);
    }
}

module.exports = SkinnyProgressBar;
<|endoftext|>


<commit_before>;(function($, window, document, undefined) {
    "use strict";

    var pluginName = 'register',
        defaults = {
            hiddenCls: 'is--hidden',
            errorCls: 'has--error'
        };

    /**
     * Plugin constructor which merges the default settings with the user settings
     * and parses the `data`-attributes of the incoming `element`.
     *
     * @param {HTMLElement} element - Element which should be used in the plugin
     * @param {Object} userOpts - User settings for the plugin
     * @returns {Void}
     * @constructor
     */
    function Plugin(element, userOpts) {
        var me = this;

        me.$el = $(element);
        me.opts = $.extend({}, defaults, userOpts);

        me._defaults = defaults;
        me._name = pluginName;

        me.init();
    }

    /**
     * Initializes the plugin, sets up event listeners and adds the necessary
     * classes to get the plugin up and running.
     *
     * @returns {Void}
     */
    Plugin.prototype.init = function() {
        var me = this;

        me.$typeSelection = me.$el.find('.register--customertype select');
        me.$skipAccount = me.$el.find('.register--check input');
        me.$alternativeShipping = me.$el.find('.register--alt-shipping input');

        me.$companyFieldset = me.$el.find('.register--company');
        me.$accountFieldset = me.$el.find('.register--account-information');
        me.$shippingFieldset = me.$el.find('.register--shipping');

        me.$inputs = me.$el.find('.is--required:input');

        me.registerEvents();
    };

    Plugin.prototype.registerEvents = function () {
        var me = this;

        me.$typeSelection.on('change.' + pluginName, $.proxy(me.onChangeType, me));
        me.$skipAccount.on('change.' + pluginName, $.proxy(me.onSkipAccount, me));
        me.$alternativeShipping.on('change.' + pluginName, $.proxy(me.onChangeShipping, me));
        me.$inputs.on('blur.' + pluginName, $.proxy(me.onValidateInput, me));
    };

    Plugin.prototype.onChangeType = function (event) {
        var me = this,
            $target = $(event.currentTarget),
            method = ($target.val() === 'business') ? 'removeClass' : 'addClass';

        me.$companyFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onSkipAccount = function () {
        var me = this,
            $target = $(event.currentTarget),
            isChecked = $target.is(':checked'),
            method = (isChecked) ? 'addClass' : 'removeClass';

        me.$accountFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onChangeShipping = function (event) {
        var me = this,
            $target = $(event.currentTarget),
            isChecked = $target.is(':checked'),
            method = (isChecked) ? 'removeClass' : 'addClass';

        me.$shippingFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onValidateInput = function (event) {
        var me = this,
            $el = $(event.currentTarget),
            id = $el.attr('id'),
            action;

        switch (id) {
            case 'register_personal_skipLogin':
            case 'register_personal_email':
            case 'register_personal_emailConfirmation':
                action = 'ajax_validate_email';
                break;
            case 'register_billing_ustid':
                action = 'ajax_validate_billing';
                break;
            case 'register_personal_password':
            case 'register_personal_passwordConfirmation':
                action = 'ajax_validate_password';
                break;
        }

        if (!$el.val()) {
            me.setFieldAsError($el);
            return;
        } else if (action) {
            me.validateUsingAjax($el, action);
            return;
        } else {
            me.setFieldAsSuccess($el);
        }
    };

    Plugin.prototype.setFieldAsError = function ($el) {
        var me = this;

        $el.addClass(me.opts.errorCls);
    };

    Plugin.prototype.validateUsingAjax = function ($el, action) {
        var me = this,
            data = 'action=' + action + '&' + me.$el.find('form').serialize();

        var collectMessages = function (result) {
            var messages = [];
            for (var error_key in result.error_messages) {
                if(result.error_messages.length) {
                    messages.push(result.error_messages[error_key]);
                }
            }
            return messages.join('
');
        };

        var onSuccess = function (result, data) {
            if (result && result.error_flags) {
                for (var error_flag in result.error_flags) {
                    if (result.error_flags[error_flag]) {
                        me.setFieldAsError(me.$el.find('.' + error_flag));
                    } else {
                        me.setFieldAsSuccess(me.$el.find('.' + error_flag));
                    }
                }
            }

            $('#' + action + '--message').remove();
            if (result && result.error_messages && result.error_messages.length) {
                $('
', {
                    'html': '
' + collectMessages(result) + '
',
                    'id': action + '--message',
                    'class': 'register--error-msg'
                }).insertAfter($el);
                me.setFieldAsError($el);
            }
        };

        $.ajax({
            'data': data,
            'type': 'post',
            'dataType': 'json',
            'url': $.controller.ajax_validate,
            'success': onSuccess
        });
    };

    Plugin.prototype.setFieldAsSuccess = function ($el) {
        var me = this;

        $el.removeClass(me.opts.errorCls);
    };

    /**
     * Destroyes the initialized plugin completely, so all event listeners will
     * be removed and the plugin data, which is stored in-memory referenced to
     * the DOM node.
     *
     * @returns {Boolean}
     */
    Plugin.prototype.destroy = function() {
        var me = this;

        me.$typeSelection.off('change.' + pluginName);
        me.$skipAccount.off('change.' + pluginName);
        me.$alternativeShipping.off('change.' + pluginName);
        me.$inputs.off('blur.' + pluginName);
    };

    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName,
                new Plugin( this, options ));
            }
        });
    };
})(jQuery, window, document);<commit_msg>SW-7906 Added: Default case for the switch<commit_after>;(function($, window, document, undefined) {
    "use strict";

    var pluginName = 'register',
        defaults = {
            hiddenCls: 'is--hidden',
            errorCls: 'has--error'
        };

    /**
     * Plugin constructor which merges the default settings with the user settings
     * and parses the `data`-attributes of the incoming `element`.
     *
     * @param {HTMLElement} element - Element which should be used in the plugin
     * @param {Object} userOpts - User settings for the plugin
     * @returns {Void}
     * @constructor
     */
    function Plugin(element, userOpts) {
        var me = this;

        me.$el = $(element);
        me.opts = $.extend({}, defaults, userOpts);

        me._defaults = defaults;
        me._name = pluginName;

        me.init();
    }

    /**
     * Initializes the plugin, sets up event listeners and adds the necessary
     * classes to get the plugin up and running.
     *
     * @returns {Void}
     */
    Plugin.prototype.init = function() {
        var me = this;

        me.$typeSelection = me.$el.find('.register--customertype select');
        me.$skipAccount = me.$el.find('.register--check input');
        me.$alternativeShipping = me.$el.find('.register--alt-shipping input');

        me.$companyFieldset = me.$el.find('.register--company');
        me.$accountFieldset = me.$el.find('.register--account-information');
        me.$shippingFieldset = me.$el.find('.register--shipping');

        me.$inputs = me.$el.find('.is--required:input');

        me.registerEvents();
    };

    Plugin.prototype.registerEvents = function () {
        var me = this;

        me.$typeSelection.on('change.' + pluginName, $.proxy(me.onChangeType, me));
        me.$skipAccount.on('change.' + pluginName, $.proxy(me.onSkipAccount, me));
        me.$alternativeShipping.on('change.' + pluginName, $.proxy(me.onChangeShipping, me));
        me.$inputs.on('blur.' + pluginName, $.proxy(me.onValidateInput, me));
    };

    Plugin.prototype.onChangeType = function (event) {
        var me = this,
            $target = $(event.currentTarget),
            method = ($target.val() === 'business') ? 'removeClass' : 'addClass';

        me.$companyFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onSkipAccount = function () {
        var me = this,
            $target = $(event.currentTarget),
            isChecked = $target.is(':checked'),
            method = (isChecked) ? 'addClass' : 'removeClass';

        me.$accountFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onChangeShipping = function (event) {
        var me = this,
            $target = $(event.currentTarget),
            isChecked = $target.is(':checked'),
            method = (isChecked) ? 'removeClass' : 'addClass';

        me.$shippingFieldset[method](me.opts.hiddenCls);
    };

    Plugin.prototype.onValidateInput = function (event) {
        var me = this,
            $el = $(event.currentTarget),
            id = $el.attr('id'),
            action;

        switch (id) {
            case 'register_personal_skipLogin':
            case 'register_personal_email':
            case 'register_personal_emailConfirmation':
                action = 'ajax_validate_email';
                break;
            case 'register_billing_ustid':
                action = 'ajax_validate_billing';
                break;
            case 'register_personal_password':
            case 'register_personal_passwordConfirmation':
                action = 'ajax_validate_password';
                break;
            default:
                return false;
                break;
        }

        if (!$el.val()) {
            me.setFieldAsError($el);
            return;
        } else if (action) {
            me.validateUsingAjax($el, action);
            return;
        } else {
            me.setFieldAsSuccess($el);
        }
    };

    Plugin.prototype.setFieldAsError = function ($el) {
        var me = this;

        $el.addClass(me.opts.errorCls);
    };

    Plugin.prototype.validateUsingAjax = function ($el, action) {
        var me = this,
            data = 'action=' + action + '&' + me.$el.find('form').serialize();

        var collectMessages = function (result) {
            var messages = [];
            for (var error_key in result.error_messages) {
                if(result.error_messages.length) {
                    messages.push(result.error_messages[error_key]);
                }
            }
            return messages.join('
');
        };

        var onSuccess = function (result, data) {
            if (result && result.error_flags) {
                for (var error_flag in result.error_flags) {
                    if (result.error_flags[error_flag]) {
                        me.setFieldAsError(me.$el.find('.' + error_flag));
                    } else {
                        me.setFieldAsSuccess(me.$el.find('.' + error_flag));
                    }
                }
            }

            $('#' + action + '--message').remove();
            if (result && result.error_messages && result.error_messages.length) {
                $('
', {
                    'html': '
' + collectMessages(result) + '
',
                    'id': action + '--message',
                    'class': 'register--error-msg'
                }).insertAfter($el);
                me.setFieldAsError($el);
            }
        };

        $.ajax({
            'data': data,
            'type': 'post',
            'dataType': 'json',
            'url': $.controller.ajax_validate,
            'success': onSuccess
        });
    };

    Plugin.prototype.setFieldAsSuccess = function ($el) {
        var me = this;

        $el.removeClass(me.opts.errorCls);
    };

    /**
     * Destroyes the initialized plugin completely, so all event listeners will
     * be removed and the plugin data, which is stored in-memory referenced to
     * the DOM node.
     *
     * @returns {Boolean}
     */
    Plugin.prototype.destroy = function() {
        var me = this;

        me.$typeSelection.off('change.' + pluginName);
        me.$skipAccount.off('change.' + pluginName);
        me.$alternativeShipping.off('change.' + pluginName);
        me.$inputs.off('blur.' + pluginName);
    };

    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName,
                new Plugin( this, options ));
            }
        });
    };
})(jQuery, window, document);<|endoftext|>


<commit_before>//This function produces better results for most layouts that do not have compound support
//unused currently.
/*
const pcGraphLayoutHack = (cy, layoutOpts) => {
  const complexes = cy.nodes().filter(node => node.isParent() && node.data('class') === 'complex');
  complexes.forEach(node => {
    if (!node.isParent()) { return; }
    node.children().layout({
      name: 'grid',
      fit: false,
      avoidOverlap: true,
      condense: true,
      rows: Math.floor(Math.sqrt(node.children().size())),
      cols: Math.floor(Math.sqrt(node.children().size()))}).run();
    node.scratch('_bb', node.boundingBox());
    node.scratch('_children', node.children());
    node.children().remove();
    node.style({width: node.scratch('_bb').w, height: node.scratch('_bb').h});
  });
  const layout = cy.layout(layoutOpts);
  layout.pon('layoutstop', () => {
    complexes.forEach(node => {
      node.scratch('_children').position(node.position());
      node.scratch('_children').restore();
      cy.zoomingEnabled(false);
      node.children().layout({
        name:'grid',
        fit: 'false',
        avoidOverlap: true,
        condense: true,
        animate: true,
        rows: Math.floor(Math.sqrt(node.children().size())),
        cols: Math.floor(Math.sqrt(node.children().size())),
        boundingBox: node.boundingBox({
          includeLabels: false
        })
      }).run();
      cy.zoomingEnabled(true);
    });

  });
  layout.run();
};
*/

const defaultLayout = {
  displayName: 'Force Directed',
  description: 'For undirected compound graphs',
  options: {
    name: 'cose-bilkent',
    nodeDimensionsIncludeLabels: true,
    tilingPaddingVertical: 20,
    tilingPaddingHorizontal: 20,
    animate: true,
    animationDuration: 500,
    fit: true,
    padding: 75,
    randomize: false
  }
};

const interactionsLayout = {
  displayName: 'Concentric',
  description: 'Align the nodes in concentric circles',
  options: {
    name: 'cose-bilkent',
    nodeRepulsion: 20000,
    edgeElasticity: 0.05,
    idealEdgeLength: 200,
    nodeDimensionsIncludeLabels: false,
    animate:false,
    fit: true,
    padding: 0,
    levelWidth:(nodes)=>nodes.maxDegree()/1000,
  }
};

const getLayoutConfig = (presetLayoutJSON) => {
  const humanCreatedLayout = {
    name: 'preset',
    displayName: 'Human-created',
    description: '',
    options: {
      name: 'preset',
      positions: node => presetLayoutJSON[node.id()],
      animate: true,
      animationDuration: 500,
      fit: true,
      padding: 100
    }
  };

  let layoutConfig;
  if (presetLayoutJSON == null) {
    layoutConfig = {
      defaultLayout: defaultLayout,
      layouts: [defaultLayout]
    };
  }
  else if(presetLayoutJSON === 'interactions'){
    layoutConfig = {
      defaultLayout: interactionsLayout,
      layouts: [interactionsLayout]
    };
  }
  else {
    layoutConfig = {
      defaultLayout: humanCreatedLayout,
      layouts: [humanCreatedLayout, defaultLayout]
    };
  }

  return layoutConfig;
};

const applyHumanLayout = (cy, layoutJSON, animateOpts = {}) => {
  const nodeIds = Object.keys(layoutJSON);

  nodeIds.forEach(id => {
    const node = cy.getElementById(id);
    const pos = layoutJSON[id];

    if (!node.isParent()) {
      node.position(pos);
    }
  });

  cy.animate(animateOpts);
};

module.exports = { getLayoutConfig, applyHumanLayout };
<commit_msg>Updated Layout<commit_after>//This function produces better results for most layouts that do not have compound support
//unused currently.
/*
const pcGraphLayoutHack = (cy, layoutOpts) => {
  const complexes = cy.nodes().filter(node => node.isParent() && node.data('class') === 'complex');
  complexes.forEach(node => {
    if (!node.isParent()) { return; }
    node.children().layout({
      name: 'grid',
      fit: false,
      avoidOverlap: true,
      condense: true,
      rows: Math.floor(Math.sqrt(node.children().size())),
      cols: Math.floor(Math.sqrt(node.children().size()))}).run();
    node.scratch('_bb', node.boundingBox());
    node.scratch('_children', node.children());
    node.children().remove();
    node.style({width: node.scratch('_bb').w, height: node.scratch('_bb').h});
  });
  const layout = cy.layout(layoutOpts);
  layout.pon('layoutstop', () => {
    complexes.forEach(node => {
      node.scratch('_children').position(node.position());
      node.scratch('_children').restore();
      cy.zoomingEnabled(false);
      node.children().layout({
        name:'grid',
        fit: 'false',
        avoidOverlap: true,
        condense: true,
        animate: true,
        rows: Math.floor(Math.sqrt(node.children().size())),
        cols: Math.floor(Math.sqrt(node.children().size())),
        boundingBox: node.boundingBox({
          includeLabels: false
        })
      }).run();
      cy.zoomingEnabled(true);
    });

  });
  layout.run();
};
*/

const defaultLayout = {
  displayName: 'Force Directed',
  description: 'For undirected compound graphs',
  options: {
    name: 'cose-bilkent',
    nodeDimensionsIncludeLabels: true,
    tilingPaddingVertical: 20,
    tilingPaddingHorizontal: 20,
    animate: true,
    animationDuration: 500,
    fit: true,
    padding: 75,
    randomize: false
  }
};

const interactionsLayout = {
  displayName: 'cose-bilkent',
  description: 'The CoSE layout for Cytoscape.js by the i-Vis Lab in Bilkent University',
  options: {
    name: 'cose-bilkent',
    nodeRepulsion: 20000,
    edgeElasticity: 0.05,
    idealEdgeLength: 200,
    animate:false,
  }
};

const getLayoutConfig = (presetLayoutJSON) => {
  const humanCreatedLayout = {
    name: 'preset',
    displayName: 'Human-created',
    description: '',
    options: {
      name: 'preset',
      positions: node => presetLayoutJSON[node.id()],
      animate: true,
      animationDuration: 500,
      fit: true,
      padding: 100
    }
  };

  let layoutConfig;
  if (presetLayoutJSON == null) {
    layoutConfig = {
      defaultLayout: defaultLayout,
      layouts: [defaultLayout]
    };
  }
  else if(presetLayoutJSON === 'interactions'){
    layoutConfig = {
      defaultLayout: interactionsLayout,
      layouts: [interactionsLayout]
    };
  }
  else {
    layoutConfig = {
      defaultLayout: humanCreatedLayout,
      layouts: [humanCreatedLayout, defaultLayout]
    };
  }

  return layoutConfig;
};

const applyHumanLayout = (cy, layoutJSON, animateOpts = {}) => {
  const nodeIds = Object.keys(layoutJSON);

  nodeIds.forEach(id => {
    const node = cy.getElementById(id);
    const pos = layoutJSON[id];

    if (!node.isParent()) {
      node.position(pos);
    }
  });

  cy.animate(animateOpts);
};

module.exports = { getLayoutConfig, applyHumanLayout };
<|endoftext|>


<commit_before>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var when = require("when");

var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js");

var palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_page");

var flowLayout = {
    flowRightEnd : 600,
    widthInterval : 300,
    heightInterval : 80
};

var previousX = -flowLayout.widthInterval;
var previousY = 0;

function addNode(type, x, y) {
    if (x !== undefined) {
        previousX = x;
        if (y !== undefined) {
            previousY = y;
        }
    } else {
        if (previousX < flowLayout.flowRightEnd) {
            previousX = previousX + flowLayout.widthInterval;
        } else {
            previousX = 0;
            previousY = previousY + flowLayout.heightInterval;
        }
    }
    browser.waitForVisible(palette.getId(type));
    browser.moveToObject(palette.getId(type));
    browser.buttonDown();
    browser.moveToObject("#red-ui-palette-search", previousX + 300, previousY + 100); // adjust to the top-left corner of workspace.
    browser.buttonUp();
    // Last node is the one that has been created right now.
    var nodeElement = browser.elements('//*[contains(concat(" ", normalize-space(@class), " "), " red-ui-flow-node-group ")][last()]');
    var nodeId = nodeElement.getAttribute('id');
    var node = nodeFactory.create(type, nodeId);
    return node;
}

function deleteAllNodes() {
    browser.waitForVisible('.red-ui-workspace-chart-event-layer');
    try {
        browser.click('.red-ui-workspace-chart-event-layer');
    } catch (e) {
        console.log(e);
    }
    browser.keys(['Control', 'a', 'a', 'Control']); // call twice to release the keys.
    browser.keys(['Delete']);
}

function deploy() {
    browser.call(function () {
        return when.promise(function(resolve, reject) {
            events.on("runtime-event", function(event) {
                if (event.id === 'runtime-deploy') {
                    resolve();
                }
            });
            browser.clickWithWait('#red-ui-header-button-deploy');
        });
    });
    browser.waitForText('#red-ui-header-button-deploy', 2000);
    // Need additional wait until buttons becomes clickable.
    browser.pause(50);
}

function init(width, height) {
    deleteAllNodes();

    if (width !== undefined) {
        flowLayout.widthInterval = width;
    }
    if (height !== undefined) {
        flowLayout.heightInterval = height;
    }
    previousX = -flowLayout.widthInterval;
    previousY = 0;
}

module.exports = {
    addNode: addNode,
    deploy: deploy,
    init: init
};
<commit_msg>when call event. remove runtime event.<commit_after>/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var when = require("when");

var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js");

var palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_page");

var flowLayout = {
    flowRightEnd : 600,
    widthInterval : 300,
    heightInterval : 80
};

var previousX = -flowLayout.widthInterval;
var previousY = 0;

function addNode(type, x, y) {
    if (x !== undefined) {
        previousX = x;
        if (y !== undefined) {
            previousY = y;
        }
    } else {
        if (previousX < flowLayout.flowRightEnd) {
            previousX = previousX + flowLayout.widthInterval;
        } else {
            previousX = 0;
            previousY = previousY + flowLayout.heightInterval;
        }
    }
    browser.waitForVisible(palette.getId(type));
    browser.moveToObject(palette.getId(type));
    browser.buttonDown();
    browser.moveToObject("#red-ui-palette-search", previousX + 300, previousY + 100); // adjust to the top-left corner of workspace.
    browser.buttonUp();
    // Last node is the one that has been created right now.
    var nodeElement = browser.elements('//*[contains(concat(" ", normalize-space(@class), " "), " red-ui-flow-node-group ")][last()]');
    var nodeId = nodeElement.getAttribute('id');
    var node = nodeFactory.create(type, nodeId);
    return node;
}

function deleteAllNodes() {
    browser.waitForVisible('.red-ui-workspace-chart-event-layer');
    try {
        browser.click('.red-ui-workspace-chart-event-layer');
    } catch (e) {
        console.log(e);
    }
    browser.keys(['Control', 'a', 'a', 'Control']); // call twice to release the keys.
    browser.keys(['Delete']);
}

function deploy() {
    browser.call(function () {
        return when.promise(function(resolve, reject) {
            events.on("runtime-event", function(event) {
                if (event.id === 'runtime-deploy') {
                    events.removeListener("runtime-event", arguments.callee);
                    resolve();
                }
            });
            browser.clickWithWait('#red-ui-header-button-deploy');
        });
    });
    browser.waitForText('#red-ui-header-button-deploy', 2000);
    // Need additional wait until buttons becomes clickable.
    browser.pause(50);
}

function init(width, height) {
    deleteAllNodes();

    if (width !== undefined) {
        flowLayout.widthInterval = width;
    }
    if (height !== undefined) {
        flowLayout.heightInterval = height;
    }
    previousX = -flowLayout.widthInterval;
    previousY = 0;
}

module.exports = {
    addNode: addNode,
    deploy: deploy,
    init: init
};
<|endoftext|>


<commit_before>define(['./module'],function(directives){
    directives.directive('editproblemcontent',function(){
        return {
            restrict: 'AE',
            scope: {
               value: '='
            },
            template: '

<textarea  ng-model="tempValue" wrap="hard" cols="74" rows="15"></textarea>',
            controller: function($scope,UserService){
                $scope.isAdministrator = UserService.isAdministrator;
            },
            link: function ( $scope, element, attrs ) {
                // Let's get a reference to the input element, as we'll want to reference it.
                var inputElement = angular.element( element.children()[1] );

                // This directive should have a set class so we can style it.
                element.addClass( 'editproblemcontent');

                // Initially, we're not editing.
                $scope.editing = false;

                $scope.$watch('value', function() {
                    $scope.tempValue = $scope.value;
                });

                // ng-click handler to activate edit-in-place
                $scope.edit = function () {
                    if ($scope.isAdministrator()){
                        $scope.editing = true;

               // We control display through a class on the directive itself. See the CSS.
                        element.addClass( 'active' );

                        // And we must focus the element.
                        // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
                        // we have to reference the first element in the array.
                        inputElement[0].focus();
                    }else{
                        element.addClass('cursorUser');
                    }
                };

                // When we leave the input, we're done editing.
                var onEdit = function() {
                    $scope.$apply(function() {
                        $scope.editing = false;
                        $scope.value = $scope.tempValue;
                        element.removeClass('active');
                    });
                };

                // When we leave the input, we're done editing.
                inputElement.prop( 'onblur', onEdit);
            }
        };
    });
});<commit_msg>Update editProblemContent.js<commit_after>define(['./module'],function(directives){
    directives.directive('editproblemcontent',function(){
        return {
            restrict: 'AE',
            scope: {
               value: '='
            },
            template: '

<textarea  ng-model="tempValue" wrap="hard" cols="74" rows="25"></textarea>',
            controller: function($scope,UserService){
                $scope.isAdministrator = UserService.isAdministrator;
            },
            link: function ( $scope, element, attrs ) {
                // Let's get a reference to the input element, as we'll want to reference it.
                var inputElement = angular.element( element.children()[1] );

                // This directive should have a set class so we can style it.
                element.addClass( 'editproblemcontent');

                // Initially, we're not editing.
                $scope.editing = false;

                $scope.$watch('value', function() {
                    $scope.tempValue = $scope.value;
                });

                // ng-click handler to activate edit-in-place
                $scope.edit = function () {
                    if ($scope.isAdministrator()){
                        $scope.editing = true;

               // We control display through a class on the directive itself. See the CSS.
                        element.addClass( 'active' );

                        // And we must focus the element.
                        // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
                        // we have to reference the first element in the array.
                        inputElement[0].focus();
                    }else{
                        element.addClass('cursorUser');
                    }
                };

                // When we leave the input, we're done editing.
                var onEdit = function() {
                    $scope.$apply(function() {
                        $scope.editing = false;
                        $scope.value = $scope.tempValue;
                        element.removeClass('active');
                    });
                };

                // When we leave the input, we're done editing.
                inputElement.prop( 'onblur', onEdit);
            }
        };
    });
});
<|endoftext|>


<commit_before>describe('L.esri.Layers.FeatureLayer', function () {
  function createMap(){
    // create container
    var container = document.createElement('div');

    // give container a width/height
    container.setAttribute('style', 'width:500px; height: 500px;');

    // add contianer to body
    document.body.appendChild(container);

    return L.map(container, {
      minZoom: 1,
      maxZoom: 19
    }).setView([45.51, -122.66], 5);
  }

  var layer;
  var map = createMap();
  var features = [({
        type: 'Feature',
        id: 1,
        geometry: {
          type: 'LineString',
          coordinates: [[-122, 45], [-121, 40]]
        },
        properties: {
          time: new Date('January 1 2014').valueOf()
        }
      }),{
    type: 'Feature',
    id: 2,
    geometry: {
      type: 'LineString',
      coordinates: [[-123, 46], [-120, 45]]
    },
    properties: {
      time: new Date('Febuary 1 2014').valueOf()
    }
  }];

  var multiPolygon = [({
      type : 'Feature',
      id: 1,
      geometry: {
        type: 'MultiPolygon',
        coordinates: [[[[-95, 43], [-95, 50], [-90, 50], [-91, 42], [-95, 43]]], [[[-89, 42], [-89, 50], [-80, 50], [-80, 42]]]]
      },
      properties: {
        time: new Date('Febuary 1 2014').valueOf()
      }
  })];

  beforeEach(function(){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      timeField: 'time',
      pointToLayer: function(feature, latlng){
        return L.circleMarker(latlng);
      }
    }).addTo(map);

    layer.createLayers(features);
  });

  it('should fire a createfeature event', function(done){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      timeField: 'time',
      pointToLayer: function(feature, latlng){
        return L.circleMarker(latlng);
      }
    }).addTo(map);

    layer.on('createfeature', function(e){
      expect(e.feature.id).to.equal(2);
      done();
    });

    layer.createLayers(features);
  });

  it('should have an alias at L.esri.Layers.featureLayer', function(){
    var layer = L.esri.Layers.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0');
    expect(layer).to.be.an.instanceof(L.esri.Layers.FeatureLayer);
  });

  it('should create features on a map', function(){
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should remove features on a map', function(){
    layer.removeLayers([1]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(false);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should fire a removefeature event', function(){
    layer.on('removefeature', function(e){
      expect(e.feature.id).to.equal(1);
    });
    layer.removeLayers([1]);
  });

  it('should add features back to a map', function(){
    layer.removeLayers([1]);
    layer.addLayers([1]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should fire a addfeature event', function(){
    layer.on('addfeature', function(e){
      expect(e.feature.id).to.equal(1);
    });
    layer.removeLayers([1]);
    layer.addLayers([1]);
  });

  it('should readd features back to a map', function(){
    map.removeLayer(layer.getFeature(1));

    layer.createLayers([{
      type: 'Feature',
      id: 1,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40], [-121.5, 42.5]]
      },
      properties: {
        time: new Date('January 1 2014').valueOf()
      }
    }]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(layer.getFeature(1).getLatLngs().length).to.equal(3);
  });

  it('should not add features outside the time range', function(){
    layer.setTimeRange(new Date('January 1 2014'), new Date('Febuary 1 2014'));

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 47]
      },
      properties: {
        time: new Date('March 1 2014').valueOf()
      }
    }]);

    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(3))).to.equal(false);
  });

  it('should be able to add itself to a map', function(){
    layer.addTo(map);

    expect(map.hasLayer(layer)).to.equal(true);
  });

  it('should be remove itself from a map', function(){
    layer.addTo(map);
    map.removeLayer(layer);

    expect(map.hasLayer(layer)).to.equal(false);
  });

  it('should bind popups to existing features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    expect(layer.getFeature(1)._popup.getContent()).to.equal('ID: 1');
    expect(layer.getFeature(2)._popup.getContent()).to.equal('ID: 2');
  });

  it('should bind popups to new features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 46]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3)._popup.getContent()).to.equal('ID: 3');
  });

  it('should preserve popup options when binding popup to new or existing features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    }, {minWidth: 500});

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 46]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(1)._popup.options.minWidth).to.equal(500);
    expect(layer.getFeature(2)._popup.options.minWidth).to.equal(500);
    expect(layer.getFeature(3)._popup.options.minWidth).to.equal(500);
  });

  it('should unbind popups on features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    layer.unbindPopup();
    expect(layer.getFeature(1)._popup).to.equal(null);
    expect(layer.getFeature(2)._popup).to.equal(null);
  });

  it('should unbind popups on multi polygon features', function(){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', { timeField: 'time' }).addTo(map);

    layer.createLayers(multiPolygon);
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    layer.unbindPopup();
    expect(layer.getFeature(1).getLayers()[0]._popup).to.equal(null);
    expect(layer.getFeature(1).getLayers()[1]._popup).to.equal(null);
  });

  it('should iterate over each feature', function(){
    var spy = sinon.spy();
    layer.eachFeature(spy);
    expect(spy.callCount).to.equal(2);
  });

  it('should run a function against every feature', function(){
    var spy = sinon.spy();
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      onEachFeature: spy
    }).addTo(map);
    layer.createLayers(features);
    expect(spy.callCount).to.equal(2);
  });

  it('should change styles on features with an object', function(){
    layer.setStyle({
      color: 'red'
    });

    expect(layer.getFeature(1).options.color).to.equal('red');
    expect(layer.getFeature(2).options.color).to.equal('red');

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40]]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3).options.color).to.equal('red');
  });

  it('should change styles on features with a function', function(){
    layer.setStyle(function(){
      return {
        color: 'red'
      };
    });

    expect(layer.getFeature(1).options.color).to.equal('red');
    expect(layer.getFeature(2).options.color).to.equal('red');

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40]]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3).options.color).to.equal('red');
  });

  it('should propagate events from individual features', function(){
    var spy = sinon.spy();
    layer.on('click', spy);

    layer.getFeature(1).fire('click', {
      foo: 'bar'
    });

    expect(spy.getCall(0).args[0].foo).to.equal('bar');
    expect(spy.getCall(0).args[0].type).to.equal('click');
  });
});
<commit_msg>add reset style on multipolygon spec<commit_after>describe('L.esri.Layers.FeatureLayer', function () {
  function createMap(){
    // create container
    var container = document.createElement('div');

    // give container a width/height
    container.setAttribute('style', 'width:500px; height: 500px;');

    // add contianer to body
    document.body.appendChild(container);

    return L.map(container, {
      minZoom: 1,
      maxZoom: 19
    }).setView([45.51, -122.66], 5);
  }

  var layer;
  var map = createMap();
  var features = [({
        type: 'Feature',
        id: 1,
        geometry: {
          type: 'LineString',
          coordinates: [[-122, 45], [-121, 40]]
        },
        properties: {
          time: new Date('January 1 2014').valueOf()
        }
      }),{
    type: 'Feature',
    id: 2,
    geometry: {
      type: 'LineString',
      coordinates: [[-123, 46], [-120, 45]]
    },
    properties: {
      time: new Date('Febuary 1 2014').valueOf()
    }
  }];

  var multiPolygon = [({
      type : 'Feature',
      id: 1,
      geometry: {
        type: 'MultiPolygon',
        coordinates: [[[[-95, 43], [-95, 50], [-90, 50], [-91, 42], [-95, 43]]], [[[-89, 42], [-89, 50], [-80, 50], [-80, 42]]]]
      },
      properties: {
        time: new Date('Febuary 1 2014').valueOf()
      }
  })];

  beforeEach(function(){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      timeField: 'time',
      pointToLayer: function(feature, latlng){
        return L.circleMarker(latlng);
      }
    }).addTo(map);

    layer.createLayers(features);
  });

  it('should fire a createfeature event', function(done){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      timeField: 'time',
      pointToLayer: function(feature, latlng){
        return L.circleMarker(latlng);
      }
    }).addTo(map);

    layer.on('createfeature', function(e){
      expect(e.feature.id).to.equal(2);
      done();
    });

    layer.createLayers(features);
  });

  it('should have an alias at L.esri.Layers.featureLayer', function(){
    var layer = L.esri.Layers.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0');
    expect(layer).to.be.an.instanceof(L.esri.Layers.FeatureLayer);
  });

  it('should create features on a map', function(){
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should remove features on a map', function(){
    layer.removeLayers([1]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(false);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should fire a removefeature event', function(){
    layer.on('removefeature', function(e){
      expect(e.feature.id).to.equal(1);
    });
    layer.removeLayers([1]);
  });

  it('should add features back to a map', function(){
    layer.removeLayers([1]);
    layer.addLayers([1]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
  });

  it('should fire a addfeature event', function(){
    layer.on('addfeature', function(e){
      expect(e.feature.id).to.equal(1);
    });
    layer.removeLayers([1]);
    layer.addLayers([1]);
  });

  it('should readd features back to a map', function(){
    map.removeLayer(layer.getFeature(1));

    layer.createLayers([{
      type: 'Feature',
      id: 1,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40], [-121.5, 42.5]]
      },
      properties: {
        time: new Date('January 1 2014').valueOf()
      }
    }]);
    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(layer.getFeature(1).getLatLngs().length).to.equal(3);
  });

  it('should not add features outside the time range', function(){
    layer.setTimeRange(new Date('January 1 2014'), new Date('Febuary 1 2014'));

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 47]
      },
      properties: {
        time: new Date('March 1 2014').valueOf()
      }
    }]);

    expect(map.hasLayer(layer.getFeature(1))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(2))).to.equal(true);
    expect(map.hasLayer(layer.getFeature(3))).to.equal(false);
  });

  it('should be able to add itself to a map', function(){
    layer.addTo(map);

    expect(map.hasLayer(layer)).to.equal(true);
  });

  it('should be remove itself from a map', function(){
    layer.addTo(map);
    map.removeLayer(layer);

    expect(map.hasLayer(layer)).to.equal(false);
  });

  it('should bind popups to existing features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    expect(layer.getFeature(1)._popup.getContent()).to.equal('ID: 1');
    expect(layer.getFeature(2)._popup.getContent()).to.equal('ID: 2');
  });

  it('should bind popups to new features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 46]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3)._popup.getContent()).to.equal('ID: 3');
  });

  it('should preserve popup options when binding popup to new or existing features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    }, {minWidth: 500});

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'Point',
        coordinates: [-123, 46]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(1)._popup.options.minWidth).to.equal(500);
    expect(layer.getFeature(2)._popup.options.minWidth).to.equal(500);
    expect(layer.getFeature(3)._popup.options.minWidth).to.equal(500);
  });

  it('should unbind popups on features', function(){
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    layer.unbindPopup();
    expect(layer.getFeature(1)._popup).to.equal(null);
    expect(layer.getFeature(2)._popup).to.equal(null);
  });

  it('should unbind popups on multi polygon features', function(){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', { timeField: 'time' }).addTo(map);

    layer.createLayers(multiPolygon);
    layer.bindPopup(function(feature){
      return 'ID: ' + feature.id;
    });
    layer.unbindPopup();
    expect(layer.getFeature(1).getLayers()[0]._popup).to.equal(null);
    expect(layer.getFeature(1).getLayers()[1]._popup).to.equal(null);
  });

  it('should reset style on multi polygon features', function(){
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      style: {
        color: 'black'
      }
    }).addTo(map);

    layer.createLayers(multiPolygon);

    layer.setFeatureStyle(1, {
      color: 'red'
    });

    expect(layer.getFeature(1).getLayers()[0].options.color).to.equal('red');

    layer.resetStyle(1);

    expect(layer.getFeature(1).getLayers()[0].options.color).to.equal('black');
  });

  it('should iterate over each feature', function(){
    var spy = sinon.spy();
    layer.eachFeature(spy);
    expect(spy.callCount).to.equal(2);
  });

  it('should run a function against every feature', function(){
    var spy = sinon.spy();
    layer = L.esri.featureLayer('http://services.arcgis.com/mock/arcgis/rest/services/MockService/MockFeatureServer/0', {
      onEachFeature: spy
    }).addTo(map);
    layer.createLayers(features);
    expect(spy.callCount).to.equal(2);
  });

  it('should change styles on features with an object', function(){
    layer.setStyle({
      color: 'red'
    });

    expect(layer.getFeature(1).options.color).to.equal('red');
    expect(layer.getFeature(2).options.color).to.equal('red');

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40]]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3).options.color).to.equal('red');
  });

  it('should change styles on features with a function', function(){
    layer.setStyle(function(){
      return {
        color: 'red'
      };
    });

    expect(layer.getFeature(1).options.color).to.equal('red');
    expect(layer.getFeature(2).options.color).to.equal('red');

    layer.createLayers([{
      type: 'Feature',
      id: 3,
      geometry: {
        type: 'LineString',
        coordinates: [[-122, 45], [-121, 40]]
      },
      properties: {
        time: new Date('Febuary 24 2014').valueOf()
      }
    }]);

    expect(layer.getFeature(3).options.color).to.equal('red');
  });

  it('should propagate events from individual features', function(){
    var spy = sinon.spy();
    layer.on('click', spy);

    layer.getFeature(1).fire('click', {
      foo: 'bar'
    });

    expect(spy.getCall(0).args[0].foo).to.equal('bar');
    expect(spy.getCall(0).args[0].type).to.equal('click');
  });
});
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';
import JSON5 from 'json5';
import { satisfies } from 'semver';
import { sync as resolveSync } from 'resolve';

import { logger } from '@storybook/node-logger';

function removeReactHmre(presets) {
  const index = presets.indexOf('react-hmre');
  if (index > -1) {
    presets.splice(index, 1);
  }
}

// Tries to load a .babelrc and returns the parsed object if successful
function loadFromPath(babelConfigPath) {
  let config;

  if (fs.existsSync(babelConfigPath)) {
    const content = fs.readFileSync(babelConfigPath, 'utf-8');
    try {
      config = /^module.exports/.test(content)
        ? require(babelConfigPath) // eslint-disable-line
        : JSON5.parse(content);
      config.babelrc = false;
      logger.info('=> Loading custom babel config');
    } catch (e) {
      logger.error(`=> Error parsing babel config file: ${e.message}`);
      throw e;
    }
  }

  if (!config) return null;

  // Remove react-hmre preset.
  // It causes issues with react-storybook.
  // We don't really need it.
  // Earlier, we fix this by running storybook in the production mode.
  // But, that hide some useful debug messages.
  if (config.presets) {
    removeReactHmre(config.presets);
  }

  if (config.env && config.env.development && config.env.development.presets) {
    removeReactHmre(config.env.development.presets);
  }

  return config;
}

function isBabelLoader8() {
  try {
    // eslint-disable-next-line import/no-dynamic-require,global-require
    const babelLoaderPkg = require(resolveSync('babel-loader/package.json', {
      basedir: process.cwd(),
    }));
    return satisfies(babelLoaderPkg.version, '>=8.0.0-0');
  } catch (e) {
    return false;
  }
}

export default async function(configDir, getDefaultConfig) {
  const projectRoot = process.cwd();
  const babelConfig =
    loadFromPath(path.resolve(configDir, '.babelrc')) ||
    loadFromPath(path.resolve(projectRoot, 'babel.config.js'));

  if (babelConfig) {
    // If the custom config uses babel's `extends` clause, then replace it with
    // an absolute path. `extends` will not work unless we do this.
    if (babelConfig.extends) {
      babelConfig.extends = path.resolve(configDir, babelConfig.extends);
    }
    return babelConfig;
  }

  return isBabelLoader8() ? getDefaultConfig() : {};
}
<commit_msg>CHANGE load-custom-babel-config file so it will try to load as JS & then as JSON, and use whichever works<commit_after>import fs from 'fs';
import path from 'path';
import JSON5 from 'json5';
import { satisfies } from 'semver';
import { sync as resolveSync } from 'resolve';

import { logger } from '@storybook/node-logger';

function removeReactHmre(presets) {
  const index = presets.indexOf('react-hmre');
  if (index > -1) {
    presets.splice(index, 1);
  }
}

// Tries to load a .babelrc and returns the parsed object if successful
function loadFromPath(babelConfigPath) {
  let config;
  const error = {};

  if (fs.existsSync(babelConfigPath)) {
    const content = fs.readFileSync(babelConfigPath, 'utf-8');

    try {
      // eslint-disable-next-line global-require, import/no-dynamic-require
      config = require(babelConfigPath);
      logger.info('=> Loading custom babel config as JS');
    } catch (e) {
      error.js = e;
    }
    try {
      config = JSON5.parse(content);
      logger.info('=> Loading custom babel config');
    } catch (e) {
      error.json = e;
    }

    if (!config) {
      logger.error(`=> Error parsing babel config file: ${babelConfigPath}

      We tried both loading as JS & JSON, neither worked.
      Maybe there's a syntax error in the file?`);
      logger.error(`=> From JS loading we got: ${error.js.message}`);
      logger.error(`=> From JSON loading we got: ${error.js.message}`);

      throw error.js;
    }

    config.babelrc = false;
  }

  if (!config) return null;

  // Remove react-hmre preset.
  // It causes issues with react-storybook.
  // We don't really need it.
  // Earlier, we fix this by running storybook in the production mode.
  // But, that hide some useful debug messages.
  if (config.presets) {
    removeReactHmre(config.presets);
  }

  if (config.env && config.env.development && config.env.development.presets) {
    removeReactHmre(config.env.development.presets);
  }

  return config;
}

function isBabelLoader8() {
  try {
    // eslint-disable-next-line import/no-dynamic-require,global-require
    const babelLoaderPkg = require(resolveSync('babel-loader/package.json', {
      basedir: process.cwd(),
    }));
    return satisfies(babelLoaderPkg.version, '>=8.0.0-0');
  } catch (e) {
    return false;
  }
}

export default async function(configDir, getDefaultConfig) {
  const projectRoot = process.cwd();
  const babelConfig =
    loadFromPath(path.resolve(configDir, '.babelrc')) ||
    loadFromPath(path.resolve(projectRoot, 'babel.config.js'));

  if (babelConfig) {
    // If the custom config uses babel's `extends` clause, then replace it with
    // an absolute path. `extends` will not work unless we do this.
    if (babelConfig.extends) {
      babelConfig.extends = path.resolve(configDir, babelConfig.extends);
    }
    return babelConfig;
  }

  return isBabelLoader8() ? getDefaultConfig() : {};
}
<|endoftext|>


<commit_before>module.exports = {
    "temp":{
        "url": "http://stationdata.wunderground.com/cgi-bin/stationlookup?format=json&maxage=10&station=ICANTERB8&units=metric&v=2.0"
    },
    "slack":{
        "enabled": false, // Whether to send messages to slack
        "webhook": "http://example.com", // Slack webhook. See "Webhook integrations" in slack integrations for more info
        "threshold": 3 // How many attempts must fail before alerting Slack
    },
    "servers": [ // List of servers to check
        // {
        // "name":"Example Server", Server name
        // "url":"http://example.com" URL to check. Redirects will show as abnormal NOTE: If using slack, protocols are a requirement
        // },
        {
            "name": "Developster Live",
            "url": "http://www.developster.io"
        },
        {
            "name": "Developster Beta",
            "url": "http://beta.developster.io"
        },
        {
            "name": "Developster API",
            "url": "http://api.developster.io"
        },
//        {
//            "name": "Developster Alpha",
//            "url": "http://alpha.developster.io",
//            "alert":false
//        },
        {
            "name": "Innovate For ChCh",
            "url": "http://innovateforchch.nz"
        },
        {
            "name": "Kohknowco",
            "url": "http://www.kohknowco.org"
        },
        {
            "name": "UPT Digital",
            "url": "http://uptdigital.com"
        }
    ],
    "colors":{
        "online": "#0F0", // Online server circle color
        "abnormal": "#F70", // Abnormal color
        "offline": "#F00", // Offline color
//        "background": "#1177FF url(pic.jpg) no-repeat center;background-size:contain", // Full page background
        "background": "#000",
        "text": "#FFF" // Color of all text
    }
};<commit_msg>Fixed 302<commit_after>module.exports = {
    "temp":{
        "url": "http://stationdata.wunderground.com/cgi-bin/stationlookup?format=json&maxage=10&station=ICANTERB8&units=metric&v=2.0"
    },
    "slack":{
        "enabled": false, // Whether to send messages to slack
        "webhook": "http://example.com", // Slack webhook. See "Webhook integrations" in slack integrations for more info
        "threshold": 3 // How many attempts must fail before alerting Slack
    },
    "servers": [ // List of servers to check
        // {
        // "name":"Example Server", Server name
        // "url":"http://example.com" URL to check. Redirects will show as abnormal NOTE: If using slack, protocols are a requirement
        // },
        {
            "name": "Developster Live",
            "url": "http://www.developster.io"
        },
        {
            "name": "Developster Beta",
            "url": "http://beta.developster.io"
        },
        {
            "name": "Developster API",
            "url": "https://api.developster.io"
        },
//        {
//            "name": "Developster Alpha",
//            "url": "http://alpha.developster.io",
//            "alert":false
//        },
        {
            "name": "Innovate For ChCh",
            "url": "http://innovateforchch.nz"
        },
        {
            "name": "Kohknowco",
            "url": "http://www.kohknowco.org"
        },
        {
            "name": "UPT Digital",
            "url": "http://uptdigital.com"
        }
    ],
    "colors":{
        "online": "#0F0", // Online server circle color
        "abnormal": "#F70", // Abnormal color
        "offline": "#F00", // Offline color
//        "background": "#1177FF url(pic.jpg) no-repeat center;background-size:contain", // Full page background
        "background": "#000",
        "text": "#FFF" // Color of all text
    }
};<|endoftext|>