<commit_before>import { observable, computed, transaction, asReference } from "mobx";
import Immutable from "seamless-immutable";
import { generate } from "shortid";
import { merge, pick, omit } from "lodash";

import ApiStore from "./api-store";
import elementMap from "../elements";
import { getParagraphStyles, getGridLinesObj, getGridLineHashes } from "../utils";

// TODO: REMOVE. Useful for testing
const allColors = [
  "#EF767A", "#456990", "#49BEAA", "#49DCB1", "#EEB868", "#EF767A", "#456990",
  "#49BEAA", "#49DCB1", "#EEB868", "#EF767A"
];

export default class SlidesStore {
  // Default slides state
  // history will be an array of slides arrays
  @observable history = asReference(Immutable.from([{
    currentSlideIndex: 0,
    currentElementIndex: null,
    paragraphStyles: {
      "Heading 1": getParagraphStyles({ fontSize: 26 }),
      "Heading 2": getParagraphStyles({ fontSize: 20 }),
      "Heading 3": getParagraphStyles({ fontSize: 11, fontWeight: 700 }),
      Body: getParagraphStyles({ fontSize: 11 }),
      "Body Small": getParagraphStyles({ fontSize: 10 }),
      Caption: getParagraphStyles({ fontSize: 11, fontStyle: "italic" })
    },
    slides: [{
      // Default first slide
      id: generate(),
      props: {},
      children: [],
      color: allColors[0]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[1]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[2]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[3]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[4]
    }
  ] }]));

  @observable historyIndex = 0;

  // Slide info
  @observable width = 0;
  @observable height = 0;
  @observable left = 0;
  @observable top = 0;
  @observable scale = 1;

  // Needed for handling cursor state and pointer events
  @observable isDragging = false;
  @observable cursorType = null;
  @observable isResizing = false;
  @observable isDraggingSlide = false;
  @observable isDraggingElement = false;
  @observable isDraggingNewElement = false;

  // TODO: move user to own store
  @observable user = null;

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get slides() {
    return this.history[this.historyIndex].slides.asMutable({ deep: true });
  }

  @computed get paragraphStyles() {
    return this.history[this.historyIndex].paragraphStyles.asMutable({ deep: true });
  }

  @computed get currentSlideIndex() {
    return this.history[this.historyIndex].currentSlideIndex;
  }

  @computed get currentElementIndex() {
    return this.history[this.historyIndex].currentElementIndex;
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentSlide() {
    return this.slides[this.currentSlideIndex];
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentElement() {
    return (this.currentElementIndex === 0 || this.currentElementIndex) ?
      this.currentSlide.children[this.currentElementIndex] :
      null;
  }

  @computed get undoDisabled() {
    return this.historyIndex === 0 || this.history.length <= 1;
  }

  @computed get redoDisabled() {
    return this.historyIndex >= this.history.length - 1;
  }

  @computed get currentState() {
    return this.history[this.historyIndex].asMutable({ deep: true });
  }

  @computed get gridLines() {
    return getGridLineHashes(
      getGridLinesObj(
        // Pass in elements to snap to
        this.currentSlide.children,
        // Start with slide edges and slide center lines
        [0, Math.floor(this.height / 2), this.height],
        [0, Math.floor(this.width / 2), this.width]
      ),
      // Ignore lines for the current element index
      // Or if we're dragging a new element, don't ignore any current elements
      this.isDraggingNewElement ? 100000 : this.currentElementIndex
    );
  }

  constructor(fileStore, slides) {
    this.fileStore = fileStore;
    this.api = new ApiStore();

    if (slides) {
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides
      }]);
    }
  }

  // TODO: Move user to own store
  setUser(userInfo) {
    this.user = userInfo;
  }

  setCanvasSize({ width, height, left, top, scale }) {
    transaction(() => {
      this.width = width;
      this.height = height;
      this.left = left;
      this.top = top;
      this.scale = scale;
    });
  }

  dropElement(elementType, extraProps) {
    const slideToAddTo = this.currentSlide;
    const newSlidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;
    const element = elementMap[elementType];
    const mergedProps = merge(element.props, extraProps);

    slideToAddTo.children.push({
      ...element,
      props: mergedProps,
      id: generate()
    });

    newSlidesArray[this.currentSlideIndex] = slideToAddTo;

    this._addToHistory({
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: slideToAddTo.children.length - 1,
      slides: newSlidesArray,
      paragraphStyles: newParagraphStyles
    });
  }

  setCurrentElementIndex(newIndex) {
    const snapshot = this.currentState;
    snapshot.currentElementIndex = newIndex;

    transaction(() => {
      const left = this.history.slice(0, this.historyIndex);
      const right = this.history.slice(this.historyIndex + 1, this.history.length);
      this.history = left.concat([snapshot], right);
    });
  }

  setSelectedSlideIndex(newSlideIndex) {
    const snapshot = this.currentState;
    snapshot.currentElementIndex = null;
    snapshot.currentSlideIndex = newSlideIndex;

    transaction(() => {
      const left = this.history.slice(0, this.historyIndex);
      const right = this.history.slice(this.historyIndex + 1, this.history.length);
      this.history = left.concat([snapshot], right);
    });
  }

  moveSlide(currentIndex, newIndex) {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;

    slidesArray.splice(newIndex, 0, slidesArray.splice(currentIndex, 1)[0]);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: newIndex,
      slides: slidesArray
    });
  }

  addSlide() {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;

    // TODO: Figure out new slide defaults/interface
    const newSlide = {
      id: generate(),
      props: {},
      children: [],
      color: allColors[6]
    };

    const index = this.currentSlideIndex + 1;
    slidesArray.splice(index, 0, newSlide);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  deleteSlide() {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;
    const index = this.currentSlideIndex === 0 ? 0 : this.currentSlideIndex - 1;

    slidesArray.splice(this.currentSlideIndex, 1);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  setCurrentElementToFrontOrBack(toFront) {
    transaction(() => {
      const slidesArray = this.slides;
      const currentChildren = slidesArray[this.currentSlideIndex].children;
      const currentChild = currentChildren.splice(this.currentElementIndex, 1);
      let index;

      if (toFront) {
        slidesArray[this.currentSlideIndex]
          .children = currentChildren.concat(currentChild);
        index = slidesArray[this.currentSlideIndex].children.length - 1;
      } else {
        slidesArray[this.currentSlideIndex]
          .children = currentChild.concat(currentChildren);
        index = 0;
      }

      this._addToHistory({
        currentSlideIndex: this.currentSlideIndex,
        currentElementIndex: index,
        slides: slidesArray
      });
    });
  }

  incrementCurrentElementIndexBy(num) {
    const slidesArray = this.slides;
    const currentChildren = slidesArray[this.currentSlideIndex].children;

    if (
      num + this.currentElementIndex < 0 ||
      num + this.currentElementIndex >= currentChildren.length
    ) {
      return;
    }

    transaction(() => {
      const currentChild = currentChildren[this.currentElementIndex];
      const sibling = currentChildren[this.currentElementIndex + num];

      currentChildren[this.currentElementIndex] = sibling;
      currentChildren[this.currentElementIndex + num] = currentChild;

      this._addToHistory({
        currentSlideIndex: this.currentSlideIndex,
        currentElementIndex: this.currentElementIndex + num,
        slides: slidesArray
      });
    });
  }

  updateElementDraggingState(isDraggingElement, isDraggingNewElement = false) {
    transaction(() => {
      this.isDragging = isDraggingElement;
      this.isDraggingElement = isDraggingElement;
      this.isDraggingNewElement = isDraggingNewElement;
    });
  }

  updateElementResizeState(isResizingElement, cursor = null) {
    transaction(() => {
      this.cursorType = cursor;
      this.isResizing = isResizingElement;
    });
  }

  updateSlideDraggingState(isDraggingSlide) {
    transaction(() => {
      this.isDragging = isDraggingSlide;
      this.isDraggingSlide = isDraggingSlide;
    });
  }

  updateElementProps(props) {
    if (!this.currentElement) {
      return;
    }

    const { paragraphStyle } = this.currentElement.props;
    const newProps = merge(this.currentElement.props, props);
    const newState = this.currentState;

    if (
      paragraphStyle !== props.paragraphStyle &&
      props.style &&
      !Object.keys(props.style).length
    ) {
      // if paragraph style changes, remove all added styles, but not any other ones affecting
      // position and word wrap
      newProps.style = omit(newProps.style, Object.keys(newState.paragraphStyles[paragraphStyle]));
    }

    newState.slides[this.currentSlideIndex].children[this.currentElementIndex].props = newProps;
    this._addToHistory(newState);
  }

  updateParagraphStyles(name, styles) {
    const newParagraphStyles = this.paragraphStyles;
    const filteredParagraphStyles = pick(styles, Object.keys(newParagraphStyles[name]));
    const filteredElementStyles = omit(styles, Object.keys(newParagraphStyles[name]));
    const slidesArray = this.slides;

    slidesArray[this.currentSlideIndex]
      .children[this.currentElementIndex]
      .props
      .style = filteredElementStyles;

    newParagraphStyles[name] = { ...newParagraphStyles[name], ...filteredParagraphStyles };

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: this.currentElementIndex,
      slides: slidesArray
    });
  }

  updateChildren(nextChild, slideIndex, elementIndex) {
    const newState = this.currentState;

    newState.slides[slideIndex].children[elementIndex].children = nextChild;
    this._addToHistory(newState);
  }

  undo() {
    // double check we're not trying to undo without history
    if (this.historyIndex === 0) {
      return;
    }

    this.historyIndex -= 1;

    if (this.historyIndex === 0 && this.fileStore.isDirty) {
      this.fileStore.setIsDirty(false);
    }
  }

  redo() {
    // Double check we've got a future to redo to
    if (this.historyIndex >= this.history.length - 1) {
      return;
    }

    this.historyIndex += 1;

    if (!this.fileStore.isDirty) {
      this.fileStore.setIsDirty(true);
    }
  }

  _addToHistory(snapshot) {
    // Only notify observers once all expressions have completed
    transaction(() => {
      // If we have a future and we do an action, remove the future.
      if (this.historyIndex < this.history.length - 1) {
        this.history = this.history.slice(0, this.historyIndex + 1);
      }

      // Wrap the new slides array in an array so they aren't concatted as individual slide objects
      this.history = this.history.concat([Immutable.from(snapshot)]);
      this.historyIndex += 1;

      // Cap history to 40 entries
      if (this.history.length > 40) {
        this.history = this.history.slice(1, this.history.length);
        this.historyIndex -= 1;
      }

      if (!this.fileStore.isDirty) {
        this.fileStore.setIsDirty(true);
      }
    });
  }

  serialize() {
    return this.slides;
  }

  deserialize(newSlides) {
    const hydratedSlides = newSlides.map((slide) => ({
      ...slide,
      children: slide.children.map((childObj) => ({
        ...childObj,
        ComponentClass: elementMap[childObj.type].ComponentClass
      }))
    }));

    transaction(() => {
      this.historyIndex = 0;
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides: hydratedSlides
      }]);
    });
  }
}
<commit_msg>previous additions to the store did not support paragraph styles for adding to the history object, updated those<commit_after>import { observable, computed, transaction, asReference } from "mobx";
import Immutable from "seamless-immutable";
import { generate } from "shortid";
import { merge, pick, omit } from "lodash";

import ApiStore from "./api-store";
import elementMap from "../elements";
import { getParagraphStyles, getGridLinesObj, getGridLineHashes } from "../utils";

// TODO: REMOVE. Useful for testing
const allColors = [
  "#EF767A", "#456990", "#49BEAA", "#49DCB1", "#EEB868", "#EF767A", "#456990",
  "#49BEAA", "#49DCB1", "#EEB868", "#EF767A"
];

export default class SlidesStore {
  // Default slides state
  // history will be an array of slides arrays
  @observable history = asReference(Immutable.from([{
    currentSlideIndex: 0,
    currentElementIndex: null,
    paragraphStyles: {
      "Heading 1": getParagraphStyles({ fontSize: 26 }),
      "Heading 2": getParagraphStyles({ fontSize: 20 }),
      "Heading 3": getParagraphStyles({ fontSize: 11, fontWeight: 700 }),
      Body: getParagraphStyles({ fontSize: 11 }),
      "Body Small": getParagraphStyles({ fontSize: 10 }),
      Caption: getParagraphStyles({ fontSize: 11, fontStyle: "italic" })
    },
    slides: [{
      // Default first slide
      id: generate(),
      props: {},
      children: [],
      color: allColors[0]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[1]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[2]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[3]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[4]
    }
  ] }]));

  @observable historyIndex = 0;

  // Slide info
  @observable width = 0;
  @observable height = 0;
  @observable left = 0;
  @observable top = 0;
  @observable scale = 1;

  // Needed for handling cursor state and pointer events
  @observable isDragging = false;
  @observable cursorType = null;
  @observable isResizing = false;
  @observable isDraggingSlide = false;
  @observable isDraggingElement = false;
  @observable isDraggingNewElement = false;

  // TODO: move user to own store
  @observable user = null;

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get slides() {
    return this.history[this.historyIndex].slides.asMutable({ deep: true });
  }

  @computed get paragraphStyles() {
    return this.history[this.historyIndex].paragraphStyles.asMutable({ deep: true });
  }

  @computed get currentSlideIndex() {
    return this.history[this.historyIndex].currentSlideIndex;
  }

  @computed get currentElementIndex() {
    return this.history[this.historyIndex].currentElementIndex;
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentSlide() {
    return this.slides[this.currentSlideIndex];
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentElement() {
    return (this.currentElementIndex === 0 || this.currentElementIndex) ?
      this.currentSlide.children[this.currentElementIndex] :
      null;
  }

  @computed get undoDisabled() {
    return this.historyIndex === 0 || this.history.length <= 1;
  }

  @computed get redoDisabled() {
    return this.historyIndex >= this.history.length - 1;
  }

  @computed get currentState() {
    return this.history[this.historyIndex].asMutable({ deep: true });
  }

  @computed get gridLines() {
    return getGridLineHashes(
      getGridLinesObj(
        // Pass in elements to snap to
        this.currentSlide.children,
        // Start with slide edges and slide center lines
        [0, Math.floor(this.height / 2), this.height],
        [0, Math.floor(this.width / 2), this.width]
      ),
      // Ignore lines for the current element index
      // Or if we're dragging a new element, don't ignore any current elements
      this.isDraggingNewElement ? 100000 : this.currentElementIndex
    );
  }

  constructor(fileStore, slides) {
    this.fileStore = fileStore;
    this.api = new ApiStore();

    if (slides) {
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides
      }]);
    }
  }

  // TODO: Move user to own store
  setUser(userInfo) {
    this.user = userInfo;
  }

  setCanvasSize({ width, height, left, top, scale }) {
    transaction(() => {
      this.width = width;
      this.height = height;
      this.left = left;
      this.top = top;
      this.scale = scale;
    });
  }

  dropElement(elementType, extraProps) {
    const slideToAddTo = this.currentSlide;
    const newSlidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;
    const element = elementMap[elementType];
    const mergedProps = merge(element.props, extraProps);

    slideToAddTo.children.push({
      ...element,
      props: mergedProps,
      id: generate()
    });

    newSlidesArray[this.currentSlideIndex] = slideToAddTo;

    this._addToHistory({
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: slideToAddTo.children.length - 1,
      slides: newSlidesArray,
      paragraphStyles: newParagraphStyles
    });
  }

  setCurrentElementIndex(newIndex) {
    const snapshot = this.currentState;
    snapshot.currentElementIndex = newIndex;

    transaction(() => {
      const left = this.history.slice(0, this.historyIndex);
      const right = this.history.slice(this.historyIndex + 1, this.history.length);
      this.history = left.concat([snapshot], right);
    });
  }

  setSelectedSlideIndex(newSlideIndex) {
    const snapshot = this.currentState;
    snapshot.currentElementIndex = null;
    snapshot.currentSlideIndex = newSlideIndex;

    transaction(() => {
      const left = this.history.slice(0, this.historyIndex);
      const right = this.history.slice(this.historyIndex + 1, this.history.length);
      this.history = left.concat([snapshot], right);
    });
  }

  moveSlide(currentIndex, newIndex) {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;

    slidesArray.splice(newIndex, 0, slidesArray.splice(currentIndex, 1)[0]);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: newIndex,
      slides: slidesArray
    });
  }

  addSlide() {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;

    // TODO: Figure out new slide defaults/interface
    const newSlide = {
      id: generate(),
      props: {},
      children: [],
      color: allColors[6]
    };

    const index = this.currentSlideIndex + 1;
    slidesArray.splice(index, 0, newSlide);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  deleteSlide() {
    const slidesArray = this.slides;
    const newParagraphStyles = this.paragraphStyles;
    const index = this.currentSlideIndex === 0 ? 0 : this.currentSlideIndex - 1;

    slidesArray.splice(this.currentSlideIndex, 1);

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  setCurrentElementToFrontOrBack(toFront) {
    transaction(() => {
      const slidesArray = this.slides;
      const currentChildren = slidesArray[this.currentSlideIndex].children;
      const currentChild = currentChildren.splice(this.currentElementIndex, 1);
      const newParagraphStyles = this.paragraphStyles;
      let index;

      if (toFront) {
        slidesArray[this.currentSlideIndex]
          .children = currentChildren.concat(currentChild);
        index = slidesArray[this.currentSlideIndex].children.length - 1;
      } else {
        slidesArray[this.currentSlideIndex]
          .children = currentChild.concat(currentChildren);
        index = 0;
      }

      this._addToHistory({
        paragraphStyles: newParagraphStyles,
        currentSlideIndex: this.currentSlideIndex,
        currentElementIndex: index,
        slides: slidesArray
      });
    });
  }

  incrementCurrentElementIndexBy(num) {
    const slidesArray = this.slides;
    const currentChildren = slidesArray[this.currentSlideIndex].children;

    if (
      num + this.currentElementIndex < 0 ||
      num + this.currentElementIndex >= currentChildren.length
    ) {
      return;
    }

    transaction(() => {
      const currentChild = currentChildren[this.currentElementIndex];
      const sibling = currentChildren[this.currentElementIndex + num];
      const newParagraphStyles = this.paragraphStyles;

      currentChildren[this.currentElementIndex] = sibling;
      currentChildren[this.currentElementIndex + num] = currentChild;

      this._addToHistory({
        paragraphStyles: newParagraphStyles,
        currentSlideIndex: this.currentSlideIndex,
        currentElementIndex: this.currentElementIndex + num,
        slides: slidesArray
      });
    });
  }

  updateElementDraggingState(isDraggingElement, isDraggingNewElement = false) {
    transaction(() => {
      this.isDragging = isDraggingElement;
      this.isDraggingElement = isDraggingElement;
      this.isDraggingNewElement = isDraggingNewElement;
    });
  }

  updateElementResizeState(isResizingElement, cursor = null) {
    transaction(() => {
      this.cursorType = cursor;
      this.isResizing = isResizingElement;
    });
  }

  updateSlideDraggingState(isDraggingSlide) {
    transaction(() => {
      this.isDragging = isDraggingSlide;
      this.isDraggingSlide = isDraggingSlide;
    });
  }

  updateElementProps(props) {
    if (!this.currentElement) {
      return;
    }

    const { paragraphStyle } = this.currentElement.props;
    const newProps = merge(this.currentElement.props, props);
    const newState = this.currentState;

    if (
      paragraphStyle !== props.paragraphStyle &&
      props.style &&
      !Object.keys(props.style).length
    ) {
      // if paragraph style changes, remove all added styles, but not any other ones affecting
      // position and word wrap
      newProps.style = omit(newProps.style, Object.keys(newState.paragraphStyles[paragraphStyle]));
    }

    newState.slides[this.currentSlideIndex].children[this.currentElementIndex].props = newProps;
    this._addToHistory(newState);
  }

  updateParagraphStyles(name, styles) {
    const newParagraphStyles = this.paragraphStyles;
    const filteredParagraphStyles = pick(styles, Object.keys(newParagraphStyles[name]));
    const filteredElementStyles = omit(styles, Object.keys(newParagraphStyles[name]));
    const slidesArray = this.slides;

    slidesArray[this.currentSlideIndex]
      .children[this.currentElementIndex]
      .props
      .style = filteredElementStyles;

    newParagraphStyles[name] = { ...newParagraphStyles[name], ...filteredParagraphStyles };

    this._addToHistory({
      paragraphStyles: newParagraphStyles,
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: this.currentElementIndex,
      slides: slidesArray
    });
  }

  updateChildren(nextChild, slideIndex, elementIndex) {
    const newState = this.currentState;

    newState.slides[slideIndex].children[elementIndex].children = nextChild;
    this._addToHistory(newState);
  }

  undo() {
    // double check we're not trying to undo without history
    if (this.historyIndex === 0) {
      return;
    }

    this.historyIndex -= 1;

    if (this.historyIndex === 0 && this.fileStore.isDirty) {
      this.fileStore.setIsDirty(false);
    }
  }

  redo() {
    // Double check we've got a future to redo to
    if (this.historyIndex >= this.history.length - 1) {
      return;
    }

    this.historyIndex += 1;

    if (!this.fileStore.isDirty) {
      this.fileStore.setIsDirty(true);
    }
  }

  _addToHistory(snapshot) {
    // Only notify observers once all expressions have completed
    transaction(() => {
      // If we have a future and we do an action, remove the future.
      if (this.historyIndex < this.history.length - 1) {
        this.history = this.history.slice(0, this.historyIndex + 1);
      }

      // Wrap the new slides array in an array so they aren't concatted as individual slide objects
      this.history = this.history.concat([Immutable.from(snapshot)]);
      this.historyIndex += 1;

      // Cap history to 40 entries
      if (this.history.length > 40) {
        this.history = this.history.slice(1, this.history.length);
        this.historyIndex -= 1;
      }

      if (!this.fileStore.isDirty) {
        this.fileStore.setIsDirty(true);
      }
    });
  }

  serialize() {
    return this.slides;
  }

  deserialize(newSlides) {
    const hydratedSlides = newSlides.map((slide) => ({
      ...slide,
      children: slide.children.map((childObj) => ({
        ...childObj,
        ComponentClass: elementMap[childObj.type].ComponentClass
      }))
    }));

    transaction(() => {
      this.historyIndex = 0;
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides: hydratedSlides
      }]);
    });
  }
}
<|endoftext|>


<commit_before>const updateElement = (workspace, elementId, newProps) => {
  const element = workspace.entities[elementId]

  return Object.assign({}, workspace, {
    ...workspace,
    entities: {
      ...workspace.entities,
      [elementId]: {
        ...element,
        ...newProps
      }
    }
  })

}

export const changePrimitiveType = (workspace, payload) => {
  const { elementId, newType } = payload

  return updateElement(workspace, elementId, { type: newType })
}

export const changePrimitiveValue = (workspace, payload) => {
  const { elementId, newValue } = payload

  return updateElement(workspace, elementId, { value: newValue })
}
<commit_msg>Remove blank line<commit_after>const updateElement = (workspace, elementId, newProps) => {
  const element = workspace.entities[elementId]

  return Object.assign({}, workspace, {
    ...workspace,
    entities: {
      ...workspace.entities,
      [elementId]: {
        ...element,
        ...newProps
      }
    }
  })
}

export const changePrimitiveType = (workspace, payload) => {
  const { elementId, newType } = payload

  return updateElement(workspace, elementId, { type: newType })
}

export const changePrimitiveValue = (workspace, payload) => {
  const { elementId, newValue } = payload

  return updateElement(workspace, elementId, { value: newValue })
}
<|endoftext|>


<commit_before>/**
 * Created with JetBrains RubyMine.
 * User: teamco
 * Date: 11/24/12
 * Time: 1:49 PM
 * To change this template use File | Settings | File Templates.
 */

/**
 * Created with JetBrains RubyMine.
 * User: teamco
 * Date: 11/17/12
 * Time: 4:10 PM
 * To change this template use File | Settings | File Templates.
 */

define([
    'modules/base',
    'modules/mvc',
    'controller/widget.controller',
    'model/widget.model',
    'view/widget.view',
    'event/widget.event.manager',
    'permission/widget.permission',
    'controller/widget/widget.map',
    'controller/widget/widget.wireframe'
], function defineWidget(Base, MVC, Controller, Model, View, EventManager, Permission, Map, Wireframe) {
    /**
     * Define Widget
     * @param opts {object}
     * @constructor
     */
    var Widget = function Widget(opts) {

        this.dom = {};

        var DEFAULTS = {
            order: 1,
            html: {
                header: false,
                footer: false,
                frameLess: false,
                opacity: 0.6
            },
            maximize: false,
            events: {
                draggable: {
                    snap: false,
                    iframeFix: true,
                    axis: false,
                    scroll: true,
                    connectToSortable: false,
                    cursor: 'move',
                    appendTo: 'parent'
//                    handle: '.header',
//                    cancel: '.header .icons li, .header input, .icons li, .menu'
                },
                resizable: {
                    iframeFix: true,
                    handles: 'n, e, s, w'
                },
                droppable: {
                    activeClass: 'widget-ui-hover',
                    hoverClass: 'widget-ui-active',
                    greedy: true,
                    tolerance: 'pointer'
                }
            }
        };

        // Init MVC
        new MVC({
            scope: this,
            config: [opts, DEFAULTS],
            components: [
                Controller,
                Model,
                View,
                EventManager,
                Permission
            ],
            render: true
        });

        /**
         * Define map
         * @type {widget.map}
         */
        this.map = new Map(this);

        /**
         * Define wireframe
         * @type {widget.wireframe}
         */
        this.wireframe = new Wireframe(this);

        /**
         * Define interactions: Drag/Resize
         * @type {{}}
         */
        this.interactions = {};

        this.observer.publish(this.eventmanager.eventList.successCreated);

    };

    return Widget.extend(Base);
});
<commit_msg>fix resize handler<commit_after>/**
 * Created with JetBrains RubyMine.
 * User: teamco
 * Date: 11/24/12
 * Time: 1:49 PM
 * To change this template use File | Settings | File Templates.
 */

/**
 * Created with JetBrains RubyMine.
 * User: teamco
 * Date: 11/17/12
 * Time: 4:10 PM
 * To change this template use File | Settings | File Templates.
 */

define([
    'modules/base',
    'modules/mvc',
    'controller/widget.controller',
    'model/widget.model',
    'view/widget.view',
    'event/widget.event.manager',
    'permission/widget.permission',
    'controller/widget/widget.map',
    'controller/widget/widget.wireframe'
], function defineWidget(Base, MVC, Controller, Model, View, EventManager, Permission, Map, Wireframe) {
    /**
     * Define Widget
     * @param opts {object}
     * @constructor
     */
    var Widget = function Widget(opts) {

        this.dom = {};

        var DEFAULTS = {
            order: 1,
            html: {
                header: false,
                footer: false,
                frameLess: false,
                opacity: 0.6
            },
            maximize: false,
            events: {
                draggable: {
                    snap: false,
                    iframeFix: true,
                    axis: false,
                    scroll: true,
                    connectToSortable: false,
                    cursor: 'move',
                    appendTo: 'parent'
//                    handle: '.header',
//                    cancel: '.header .icons li, .header input, .icons li, .menu'
                },
                resizable: {
                    iframeFix: true,
                    handles: 'all'
                },
                droppable: {
                    activeClass: 'widget-ui-hover',
                    hoverClass: 'widget-ui-active',
                    greedy: true,
                    tolerance: 'pointer'
                }
            }
        };

        // Init MVC
        new MVC({
            scope: this,
            config: [opts, DEFAULTS],
            components: [
                Controller,
                Model,
                View,
                EventManager,
                Permission
            ],
            render: true
        });

        /**
         * Define map
         * @type {widget.map}
         */
        this.map = new Map(this);

        /**
         * Define wireframe
         * @type {widget.wireframe}
         */
        this.wireframe = new Wireframe(this);

        /**
         * Define interactions: Drag/Resize
         * @type {{}}
         */
        this.interactions = {};

        this.observer.publish(this.eventmanager.eventList.successCreated);

    };

    return Widget.extend(Base);
});
<|endoftext|>


<commit_before>console.log("🔍 Creating document data for Lunr...");

const yaml = require('js-yaml');

// ♻ LOOP THRU ALL THE FILES AND BUILD UP THE JSON
const fs = require('fs');
const path = require('path');

/**
 * Explores recursively a directory and returns all the filepaths and folderpaths in the callback.
 *
 * @see http://stackoverflow.com/a/5827895/4241030
 * @see https://ourcodeworld.com/articles/read/420/how-to-read-recursively-a-directory-in-node-js
 * @param {String} dir
 * @param {Function} done
 */
function directoryWalker(dir, done) {
	let results = [];
	let paths = [];
	let pagetitles = [];

	fs.readdir(dir, function(err, list) {
		if (err) return done(err);

		var pending = list.length;

		if (!pending) return done(null, results);

		list.forEach(function(file) {
			file = path.resolve(dir, file);

			fs.stat(file, function(err, stat) {
				// If directory, execute a recursive call
				if (stat && stat.isDirectory()) {
					// Add directory to array [comment if you need to remove the directories from the array]
					results.push(file);

					directoryWalker(file, function(err, res) {
						results = results.concat(res);
						if (!--pending) done(null, results);
					});
				} else {
					//results.push(file);

					if (!--pending) done(null, results);
				}
			});
		});
	});
};

////////////////////////

directoryWalker("./content/", function(err, data) {
	if (err) {
		throw err;
	}
	//console.log(data);
	const util = require('util')
	console.log(util.inspect(data, { maxArrayLength: null }))
});

console.log("current dir:" + __dirname)

// 💾 WRITE THE FILE
// fs.writeFile('site/documents.json', serialisedIndex, (err) => {
//   if (err) throw err;
//   console.log('💾 The Lunr document index has been created -> site/documents.json');
// });
<commit_msg>Strip out the root directory<commit_after>console.log("🔍 Creating document data for Lunr...");

const yaml = require('js-yaml');

// ♻ LOOP THRU ALL THE FILES AND BUILD UP THE JSON
const fs = require('fs');
const path = require('path');
const rootdir = path.dirname(__dirname)+"/content/";

/**
 * Explores recursively a directory and returns all the filepaths and folderpaths in the callback.
 *
 * @see http://stackoverflow.com/a/5827895/4241030
 * @see https://ourcodeworld.com/articles/read/420/how-to-read-recursively-a-directory-in-node-js
 * @param {String} dir
 * @param {Function} done
 */
function directoryWalker(dir, done) {
	let results = [];
	let documents = [];

	fs.readdir(dir, function(err, list) {
		if (err) return done(err);

		var pending = list.length;

		if (!pending) return done(null, results, documents);

		list.forEach(function(file) {
			file = path.resolve(dir, file);

			fs.stat(file, function(err, stat) {
				// If directory, execute a recursive call
				if (stat && stat.isDirectory()) {
					// Add directory to array [comment if you need to remove the directories from the array]

					let document = new Object();
					document.title = "chris"
					document.path = file.replace(rootdir,"/");
					documents.push(document);

					results.push(file);

					directoryWalker(file, function(err, res, docs) {
						results = results.concat(res);
						documents = documents.concat(docs);
						if (!--pending) done(null, results, documents);
					});
				} else {
					//results.push(file);

					if (!--pending) done(null, results, documents);
				}
			});
		});
	});
};

////////////////////////

directoryWalker("./content/", function(err, dirs, documents) {
	if (err) {
		throw err;
	}
	//console.log(dirs);
	const util = require('util')
	console.log(util.inspect(documents, { maxArrayLength: null }))
});

// 💾 WRITE THE FILE
// fs.writeFile('site/documents.json', serialisedIndex, (err) => {
//   if (err) throw err;
//   console.log('💾 The Lunr document index has been created -> site/documents.json');
// });
<|endoftext|>


<commit_before>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // load all grunt tasks
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // configurable paths
    var yeomanConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        yeoman: yeomanConfig,
        watch: {
            coffee: {
                files: ['<%%= yeoman.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass']
            },
            livereload: {
                files: [
                    '<%%= yeoman.app %>/*.html',
                    '{.tmp,<%%= yeoman.app %>}/styles/{,*/}*.css',
                    '{.tmp,<%%= yeoman.app %>}/scripts/{,*/}*.js',
                    '<%%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}'
                ],
                tasks: ['livereload']
            }
        },
        connect: {
            options: {
                port: 9000,
                // change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function (connect) {
                        return [
                            lrSnippet,
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'app')
                        ];
                    }
                }
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            },
            dist: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, 'dist')
                        ];
                    }
                }
            }
        },
        open: {
            server: {
                path: 'http://localhost:<%%= connect.options.port %>'
            }
        },
        clean: {
            dist: ['.tmp', '<%%= yeoman.dist %>/*'],
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/scripts/{,*/}*.js',
                '!<%%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    // rather than compiling multiple files here you should
                    // require them into your main .coffee file
                    expand: true,
                    cwd: '<%%= yeoman.app %>/scripts',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: '.tmp/spec',
                    src: '*.coffee',
                    dest: 'test/spec'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                imagesDir: '<%%= yeoman.app %>/images',
                javascriptsDir: '<%%= yeoman.app %>/scripts',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: 'app/components',
                relativeAssets: true
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/<% if (includeRequireJS) { %>
        requirejs: {
            dist: {
                // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
                options: {
                    // `name` and `out` is set by grunt-usemin
                    baseUrl: 'app/scripts',
                    optimize: 'none',
                    // TODO: Figure out how to make sourcemaps work with grunt-usemin
                    // https://github.com/yeoman/grunt-usemin/issues/30
                    //generateSourceMaps: true,
                    // required to support SourceMaps
                    // http://requirejs.org/docs/errors.html#sourcemapcomments
                    preserveLicenseComments: false,
                    useStrict: true,
                    wrap: true,
                    //uglify2: {} // https://github.com/mishoo/UglifyJS2
                }
            }
        },<% } else { %>
        uglify: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/scripts/main.js': [
                        '<%%= yeoman.app %>/scripts/{,*/}*.js'
                    ],
                }
            }
        },<% } %>
        useminPrepare: {
            html: '<%%= yeoman.app %>/index.html',
            options: {
                dest: '<%%= yeoman.dist %>'
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css'],
            options: {
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%%= yeoman.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>',
                    src: '*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}'
                    ]
                }]
            }
        }<% if (includeRequireJS) { %>,
        bower: {
            all: {
                rjsConfig: '<%%= yeoman.app %>/scripts/main.js'
            }
        }<% } %>
    });

    grunt.renameTask('regarde', 'watch');

    grunt.registerTask('server', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'coffee:dist',
            'compass:server',
            'livereload-start',
            'connect:livereload',
            'open',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'clean:server',
        'coffee',
        'compass',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'coffee',
        'compass:dist',
        'useminPrepare',<% if (includeRequireJS) { %>
        'requirejs',<% } %>
        'imagemin',
        'svgmin',
        'htmlmin',
        'concat',
        'cssmin',
        'uglify',
        'copy',
        'usemin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'test',
        'build'
    ]);
};
<commit_msg>Fix Coffee specs compilation<commit_after>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // load all grunt tasks
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // configurable paths
    var yeomanConfig = {
        app: 'app',
        dist: 'dist'
    };

    grunt.initConfig({
        yeoman: yeomanConfig,
        watch: {
            coffee: {
                files: ['<%%= yeoman.app %>/scripts/{,*/}*.coffee'],
                tasks: ['coffee:dist']
            },
            coffeeTest: {
                files: ['test/spec/{,*/}*.coffee'],
                tasks: ['coffee:test']
            },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass']
            },
            livereload: {
                files: [
                    '<%%= yeoman.app %>/*.html',
                    '{.tmp,<%%= yeoman.app %>}/styles/{,*/}*.css',
                    '{.tmp,<%%= yeoman.app %>}/scripts/{,*/}*.js',
                    '<%%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}'
                ],
                tasks: ['livereload']
            }
        },
        connect: {
            options: {
                port: 9000,
                // change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function (connect) {
                        return [
                            lrSnippet,
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'app')
                        ];
                    }
                }
            },
            test: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'test')
                        ];
                    }
                }
            },
            dist: {
                options: {
                    middleware: function (connect) {
                        return [
                            mountFolder(connect, 'dist')
                        ];
                    }
                }
            }
        },
        open: {
            server: {
                path: 'http://localhost:<%%= connect.options.port %>'
            }
        },
        clean: {
            dist: ['.tmp', '<%%= yeoman.dist %>/*'],
            server: '.tmp'
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/scripts/{,*/}*.js',
                '!<%%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://localhost:<%%= connect.options.port %>/index.html']
                }
            }
        },
        coffee: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/scripts',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/scripts',
                    ext: '.js'
                }]
            },
            test: {
                files: [{
                    expand: true,
                    cwd: '.tmp/spec',
                    src: '{,*/}*.coffee',
                    dest: '.tmp/spec',
                    ext: '.js'
                }]
            }
        },
        compass: {
            options: {
                sassDir: '<%%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                imagesDir: '<%%= yeoman.app %>/images',
                javascriptsDir: '<%%= yeoman.app %>/scripts',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: 'app/components',
                relativeAssets: true
            },
            dist: {},
            server: {
                options: {
                    debugInfo: true
                }
            }
        },
        // not used since Uglify task does concat,
        // but still available if needed
        /*concat: {
            dist: {}
        },*/<% if (includeRequireJS) { %>
        requirejs: {
            dist: {
                // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
                options: {
                    // `name` and `out` is set by grunt-usemin
                    baseUrl: 'app/scripts',
                    optimize: 'none',
                    // TODO: Figure out how to make sourcemaps work with grunt-usemin
                    // https://github.com/yeoman/grunt-usemin/issues/30
                    //generateSourceMaps: true,
                    // required to support SourceMaps
                    // http://requirejs.org/docs/errors.html#sourcemapcomments
                    preserveLicenseComments: false,
                    useStrict: true,
                    wrap: true,
                    //uglify2: {} // https://github.com/mishoo/UglifyJS2
                }
            }
        },<% } else { %>
        uglify: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/scripts/main.js': [
                        '<%%= yeoman.app %>/scripts/{,*/}*.js'
                    ],
                }
            }
        },<% } %>
        useminPrepare: {
            html: '<%%= yeoman.app %>/index.html',
            options: {
                dest: '<%%= yeoman.dist %>'
            }
        },
        usemin: {
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css'],
            options: {
                dirs: ['<%%= yeoman.dist %>']
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%%= yeoman.dist %>/images'
                }]
            }
        },
        cssmin: {
            dist: {
                files: {
                    '<%%= yeoman.dist %>/styles/main.css': [
                        '.tmp/styles/{,*/}*.css',
                        '<%%= yeoman.app %>/styles/{,*/}*.css'
                    ]
                }
            }
        },
        htmlmin: {
            dist: {
                options: {
                    /*removeCommentsFromCDATA: true,
                    // https://github.com/yeoman/grunt-usemin/issues/44
                    //collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeAttributeQuotes: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true*/
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>',
                    src: '*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },
        // Put files not handled in other tasks here
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,txt}',
                        '.htaccess',
                        'images/{,*/}*.{webp,gif}'
                    ]
                }]
            }
        }<% if (includeRequireJS) { %>,
        bower: {
            all: {
                rjsConfig: '<%%= yeoman.app %>/scripts/main.js'
            }
        }<% } %>
    });

    grunt.renameTask('regarde', 'watch');

    grunt.registerTask('server', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'coffee:dist',
            'compass:server',
            'livereload-start',
            'connect:livereload',
            'open',
            'watch'
        ]);
    });

    grunt.registerTask('test', [
        'clean:server',
        'coffee',
        'compass',
        'connect:test',
        'mocha'
    ]);

    grunt.registerTask('build', [
        'clean:dist',
        'coffee',
        'compass:dist',
        'useminPrepare',<% if (includeRequireJS) { %>
        'requirejs',<% } %>
        'imagemin',
        'svgmin',
        'htmlmin',
        'concat',
        'cssmin',
        'uglify',
        'copy',
        'usemin'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'test',
        'build'
    ]);
};
<|endoftext|>


<commit_before>'use strict';

var UserService = require('../domain/services/users/user-service');
var jwt = require('jsonwebtoken');
var config = require('../config/config');
var appConstants = require('../config/app-constants');
var EntityWithFilterMustExistSpec = require('./specifications/entity-with-filter-must-exist-spec');
var q = require('q');
var moment = require('moment');
var messages = require('../errors-messages/messages-application').authentication;

class AuthenticationService {
  constructor(userService) {
    this._userService = userService || new UserService();
    this._userMustExistWithNameAndPasswordSpec = new EntityWithFilterMustExistSpec(this._userService,
      messages.invalidUsernameOrPassword.message,
      messages.invalidUsernameOrPassword.code);
  }

  _createToken(username, id) {
    return jwt.sign({ username: username, _id: id, permissions: [appConstants.mustBeAuthenticatedPermission] }, config.secret, {
      expiresIn: '1h' // expires in 1 hour
    });
  }

  _createAuthenticationResponse(username, userId) {
    // create a token
    let token = this._createToken(username, userId);
    return { token: token, id: userId, expiresAt: moment().add(1, 'hour').format() };
  }

  registerAndAuthenticate(userViewModel) {

    return this._userService.save(userViewModel)
      .then((newEntity) => {
        return q(this._createAuthenticationResponse(newEntity.username, newEntity._id));
      })
      .catch((err) => {
        return q.reject(err);
      });
  }

  /**
  *
  */
  authenticate(authenticateViewModel) {

    return this._userMustExistWithNameAndPasswordSpec.isSatisfiedBy(authenticateViewModel)
      .then((user) => {
        return q(this._createAuthenticationResponse(user.username, user._id));
      })
      .catch((err) => {
        return q.reject(err);
      });
  }
}

module.exports = AuthenticationService;
<commit_msg>add expiration time on token<commit_after>'use strict';

var UserService = require('../domain/services/users/user-service');
var jwt = require('jsonwebtoken');
var config = require('../config/config');
var appConstants = require('../config/app-constants');
var EntityWithFilterMustExistSpec = require('./specifications/entity-with-filter-must-exist-spec');
var q = require('q');
var moment = require('moment');
var messages = require('../errors-messages/messages-application').authentication;

class AuthenticationService {
  constructor(userService) {
    this._userService = userService || new UserService();
    this._userMustExistWithNameAndPasswordSpec = new EntityWithFilterMustExistSpec(this._userService,
      messages.invalidUsernameOrPassword.message,
      messages.invalidUsernameOrPassword.code);
  }

  _createToken(username, id) {
    return jwt.sign({ username: username, _id: id, permissions: [appConstants.mustBeAuthenticatedPermission] }, config.secret, {
      expiresIn: '72h' // expires in 1 hour
    });
  }

  _createAuthenticationResponse(username, userId) {
    // create a token
    let token = this._createToken(username, userId);
    return { token: token, id: userId, expiresAt: moment().add(1, 'hour').format() };
  }

  registerAndAuthenticate(userViewModel) {

    return this._userService.save(userViewModel)
      .then((newEntity) => {
        return q(this._createAuthenticationResponse(newEntity.username, newEntity._id));
      })
      .catch((err) => {
        return q.reject(err);
      });
  }

  /**
  *
  */
  authenticate(authenticateViewModel) {

    return this._userMustExistWithNameAndPasswordSpec.isSatisfiedBy(authenticateViewModel)
      .then((user) => {
        return q(this._createAuthenticationResponse(user.username, user._id));
      })
      .catch((err) => {
        return q.reject(err);
      });
  }
}

module.exports = AuthenticationService;
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  require('time-grunt')(grunt);

  require('jit-grunt')(grunt,  {
    includereplace: 'grunt-include-replace',
    useminPrepare: 'grunt-usemin',
    validation: 'grunt-html-validation',
    replace: 'grunt-text-replace'
  });

  // Project configuration.
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    // Configs
    xh: {
      src: 'src',
      dist: 'dist',
      build: ['head.html', 'scripts.html']
    },

    useminPrepare: {
      html: {
        src: '<%%= xh.build %>',
        cwd: '<%%= xh.src %>/includes/',
        expand: true
      },

      options: {
        dest: '<%%= xh.dist %>',
        root: '<%%= xh.src %>',
        flow: {
          steps: {'js': ['concat'], 'css': ['concat'] },
          post: {}
        }
      }
    },

    usemin: {
      html: {
        src: '<%%= xh.build %>',
        cwd: '<%%= xh.src %>/includes/',
        expand: true
      },

      options: {
        assetsDirs: ['<%%= xh.src %>/includes/']
      }
    },

    clean: {
      src: [".tmp"]
    },

    // HTML Includes
    includereplace: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%%= xh.src %>',
          src: ['*.html'],
          dest: '<%%= xh.dist %>',
          ext: '.html'
        }]
      }
    },

    jsbeautifier: {
      options : {
        html: {
          indentSize: 2
        },
        js: {
          indentSize: 2
        }
      },

      html: {
        src: '<%%= xh.dist %>/*.html'
      },

      js: {
        src: '<%%= xh.dist %>/js/main.js'
      }
    },

    validation: {
      src: ['<%%= xh.dist %>/*.html'],
      options: {
        reset: true
      }
    },

    // CSS
    <% if (cssPreprocessor === 'SCSS') { %>
    sass: {
      dist: {
        options: {
          style: 'expanded',
          sourcemap: true,
          loadPath: 'src/bower_components/'
        },
        files: {
          '<%%= xh.dist %>/css/main.css': '<%%= xh.src %>/scss/main.scss'
        }
      }
    },<% } %> <% if (cssPreprocessor === 'LESS') { %>

    less: {
      dist: {
        options: {
          path: 'src/bower_components/'
        },
        files: {
          '<%%= xh.dist %>/css/main.css': '<%%= xh.src %>/less/main.less'
        }
      }
    },<% } %>

    autoprefixer: {
      main: {
        src: '<%%= xh.dist %>/css/main.css',
        dest: '<%%= xh.dist %>/css/main.css'
      }
    },

    cssbeautifier: {
      files: ['<%%= xh.dist %>/css/*.css', '!<%%= xh.dist %>/css/libraries.min.css'],
      options : {
        indent: '  '
      }
    },

    // JS
    copy: {
      normalize: {
        src: 'src/bower_components/normalize.css/normalize.css',<% if (cssPreprocessor === 'SCSS') { %>
        dest: 'src/bower_components/normalize.css/normalize.scss'<% } %><% if (cssPreprocessor === 'LESS') { %>
        dest: 'src/bower_components/normalize.css/normalize.less'<% } %>
      },

      jquery: {
        cwd: 'src/bower_components/jquery/dist/',
        src: 'jquery.min.js',
        dest: '<%%= xh.dist %>/js/',
        expand: true
      },

      js: {
        cwd: '<%%= xh.src %>/js/',
        src: 'main.js',
        dest: '<%%= xh.dist %>/js/',
        expand: true
      },<% if (isWP) { %>

      wp: {
        cwd: '<%%= xh.dist %>/',
        src: ['**', '!_xprecise', '!*.html'],
        dest: '<%= wpThemeFolder  %>',
        expand: true
      },<% } %>

      // Backup include files
      backup: {
        cwd: '<%%= xh.src %>/includes/',
        src: '<%%= xh.build %>',
        dest: '.tmp',
        expand: true
      },

      // Restore include files
      restore: {
        cwd: '.tmp',
        src: '<%%= xh.build %>',
        dest: '<%%= xh.src %>/includes/',
        expand: true
      }
    },

    jshint: {
      options: {
        jshintrc: true,
        force: true
      },
      dist: {
        src: ['<%%= xh.src %>/js/main.js', '<%%= xh.dist %>/js/main.js'],
      }
    },<% if (useModernizr) { %>

    uglify: {
      modernizr: {
        files: {
          'src/bower_components/modernizr/modernizr.min.js': ['src/bower_components/modernizr/modernizr.js']
        }
      }
    },<% } %>

    // Remplacements in main.css and main.js
    replace: {
      css: {
        src: ['<%%= xh.dist %>/css/main.css'],
        overwrite: true,
        replacements: [{
          from: '@@timestamp',
          to: '<%%= grunt.template.today() %>'
        },
        // Table of contents in main.css
        {
          from: '@@toc',
          to: function () {

            if (!grunt.file.exists('csstoc.json')) {
              return '';
            }

            var toc_file = grunt.file.readJSON('csstoc.json')
            var files = toc_file.results;
            var toc = '';
            var i = 1;
            var match;

            function capitalize(s) {
              return s[0].toUpperCase() + s.slice(1);
            }

            for (var key in files) {
              if (files.hasOwnProperty(key)) {

                var results = files[key];

                for (var key in results) {
                  if (results.hasOwnProperty(key)) {

                    match = results[key]['match'];
                    match = match.replace(/"|'|@import|;|.scss|.less/gi, "").trim();
                    match = match.split('/').pop();
                    match = capitalize(match);

                    if (match !== 'Variables' && match !== 'Mixins') {
                      toc += '\n    ' + i + '. ' + match;
                      i++;
                    }
                  }
                }
              }
            }
            return toc;
          }
        },
        // Add empty line after section & subsection comment
        {
          from: /=== \*\//g,
          to: '=== */\n'
        },
        // Add empty line after rule if it doesn't have one already
        {
          from: /}(?!\n\n)/gi,
          to: '}\n'
        }]
      },

      js: {
        src: ['<%%= xh.dist %>/js/main.js'],
        overwrite: true,
        replacements: [{
          from: '@@timestamp',
          to: '<%%= grunt.template.today() %>'
        }]
      },

      xprecise: {
        src: ['<%%= xh.src %>/includes/scripts.html'],
        overwrite: true,
        replacements: [{
          from: '<script src="http://cssonsails.org/xprecise/xprecise.min.js"></script>',
          to: ''
        }]
      }
    },

    // Create list of @imports
    search: {
      imports: {
        files: {<% if (cssPreprocessor === 'SCSS') { %>
            src: ['<%%= xh.src %>/scss/main.scss']<% } %><% if (cssPreprocessor === 'LESS') { %>
            src: ['<%%= xh.src %>/less/main.less']<% } %>
        },
        options: {
          searchString: /@import[ \("']*([^;]+)[;\)"']*/g,
          logFormat: "json",
          logFile: "csstoc.json"
        }
      }
    },

    // Watch
    watch: {
      options: {
        spawn: false
      },<% if (cssPreprocessor === 'SCSS') { %>

      scss: {
        files: ['<%%= xh.src %>/scss/*.scss'],
        tasks: ['sass', 'autoprefixer', 'cssbeautifier', 'search', 'replace:css'<% if (isWP) { %>, 'copy:wp'<% } %>]
      },<% } %><% if (cssPreprocessor === 'LESS') { %>

      less: {
        files: ['<%%= xh.src %>/less/*.less'],
        tasks: ['less', 'autoprefixer', 'cssbeautifier', 'search', 'replace:css'<% if (isWP) { %>, 'copy:wp'<% } %>]
      },<% } %>

      css: {
        files: ['<%%= xh.dist %>/css/*.css'],
        options: {
          livereload: true
        }
      },

      html: {
        files: ['<%%= xh.src %>/*.html', '<%%= xh.src %>/includes/*.html'],
        tasks: [
          'useminPrepare',
          'concat',
          'copy:backup',
          'usemin',
          'includereplace',
          'copy:restore',
          'jsbeautifier:html',<% if (isWP) { %>
          'copy:wp',<% } %>
          'clean'
        ],
        options: {
          livereload: true
        }
      },

      js: {
        files: '<%%= xh.src %>/js/*.js',
        tasks: ['copy:js', 'jsbeautifier:js', 'replace:js', 'jshint'<% if (isWP) { %>, 'copy:wp'<% } %>],
        options: {
          livereload: true
        }
      }
    }

  });

  grunt.registerTask('default', [
    // HTML
    'useminPrepare',
    'concat',
    'copy:backup',
    'usemin',
    'includereplace',
    'copy:restore',
    'jsbeautifier:html',
    'clean',

    // CSS
    <% if (cssPreprocessor === 'SCSS') { %>
    'sass',<% } %><% if (cssPreprocessor === 'LESS') { %>
    'less',<% } %>
    'autoprefixer',
    'cssbeautifier',

    // JS
    'copy:js',
    'jsbeautifier:js',

    // Replacements
    'search',
    'replace:css',
    'replace:js',<% if (isWP) { %>

    'copy:wp',<% } %>

    // Checks
    'jshint'
  ]);

  grunt.registerTask('validate', [
    'validation'
  ]);

  grunt.registerTask('qa', [
    'replace:xprecise',
    'default',
    'validate',
    'jshint'
  ]);

  grunt.registerTask('postinstall', [<% if (!useBootstrap) { %>
    'copy:normalize',<% } %><% if (useModernizr) { %>
    'uglify:modernizr',<% } %>
    'copy:jquery'
  ]);
};

<commit_msg>Added new clean task<commit_after>module.exports = function(grunt) {

  require('time-grunt')(grunt);

  require('jit-grunt')(grunt,  {
    includereplace: 'grunt-include-replace',
    useminPrepare: 'grunt-usemin',
    validation: 'grunt-html-validation',
    replace: 'grunt-text-replace'
  });

  // Project configuration.
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    // Configs
    xh: {
      src: 'src',
      dist: 'dist',
      build: ['head.html', 'scripts.html']
    },

    useminPrepare: {
      html: {
        src: '<%%= xh.build %>',
        cwd: '<%%= xh.src %>/includes/',
        expand: true
      },

      options: {
        dest: '<%%= xh.dist %>',
        root: '<%%= xh.src %>',
        flow: {
          steps: {'js': ['concat'], 'css': ['concat'] },
          post: {}
        }
      }
    },

    usemin: {
      html: {
        src: '<%%= xh.build %>',
        cwd: '<%%= xh.src %>/includes/',
        expand: true
      },

      options: {
        assetsDirs: ['<%%= xh.src %>/includes/']
      }
    },

    clean: {
      tmp: { src: ['.tmp'] },
      dist: { src: ['<%%= xh.dist %>'] }
    },

    // HTML Includes
    includereplace: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%%= xh.src %>',
          src: ['*.html'],
          dest: '<%%= xh.dist %>',
          ext: '.html'
        }]
      }
    },

    jsbeautifier: {
      options : {
        html: {
          indentSize: 2
        },
        js: {
          indentSize: 2
        }
      },

      html: {
        src: '<%%= xh.dist %>/*.html'
      },

      js: {
        src: '<%%= xh.dist %>/js/main.js'
      }
    },

    validation: {
      src: ['<%%= xh.dist %>/*.html'],
      options: {
        reset: true
      }
    },

    // CSS
    <% if (cssPreprocessor === 'SCSS') { %>
    sass: {
      dist: {
        options: {
          style: 'expanded',
          sourcemap: true,
          loadPath: 'src/bower_components/'
        },
        files: {
          '<%%= xh.dist %>/css/main.css': '<%%= xh.src %>/scss/main.scss'
        }
      }
    },<% } %> <% if (cssPreprocessor === 'LESS') { %>

    less: {
      dist: {
        options: {
          path: 'src/bower_components/'
        },
        files: {
          '<%%= xh.dist %>/css/main.css': '<%%= xh.src %>/less/main.less'
        }
      }
    },<% } %>

    autoprefixer: {
      main: {
        src: '<%%= xh.dist %>/css/main.css',
        dest: '<%%= xh.dist %>/css/main.css'
      }
    },

    cssbeautifier: {
      files: ['<%%= xh.dist %>/css/*.css', '!<%%= xh.dist %>/css/libraries.min.css'],
      options : {
        indent: '  '
      }
    },

    // JS
    copy: {
      normalize: {
        src: 'src/bower_components/normalize.css/normalize.css',<% if (cssPreprocessor === 'SCSS') { %>
        dest: 'src/bower_components/normalize.css/normalize.scss'<% } %><% if (cssPreprocessor === 'LESS') { %>
        dest: 'src/bower_components/normalize.css/normalize.less'<% } %>
      },

      jquery: {
        cwd: 'src/bower_components/jquery/dist/',
        src: 'jquery.min.js',
        dest: '<%%= xh.dist %>/js/',
        expand: true
      },

      js: {
        cwd: '<%%= xh.src %>/js/',
        src: 'main.js',
        dest: '<%%= xh.dist %>/js/',
        expand: true
      },<% if (isWP) { %>

      wp: {
        cwd: '<%%= xh.dist %>/',
        src: ['**', '!_xprecise', '!*.html'],
        dest: '<%= wpThemeFolder  %>',
        expand: true
      },<% } %>

      // Backup include files
      backup: {
        cwd: '<%%= xh.src %>/includes/',
        src: '<%%= xh.build %>',
        dest: '.tmp',
        expand: true
      },

      // Restore include files
      restore: {
        cwd: '.tmp',
        src: '<%%= xh.build %>',
        dest: '<%%= xh.src %>/includes/',
        expand: true
      }
    },

    jshint: {
      options: {
        jshintrc: true,
        force: true
      },
      dist: {
        src: ['<%%= xh.src %>/js/main.js', '<%%= xh.dist %>/js/main.js'],
      }
    },<% if (useModernizr) { %>

    uglify: {
      modernizr: {
        files: {
          'src/bower_components/modernizr/modernizr.min.js': ['src/bower_components/modernizr/modernizr.js']
        }
      }
    },<% } %>

    // Remplacements in main.css and main.js
    replace: {
      css: {
        src: ['<%%= xh.dist %>/css/main.css'],
        overwrite: true,
        replacements: [{
          from: '@@timestamp',
          to: '<%%= grunt.template.today() %>'
        },
        // Table of contents in main.css
        {
          from: '@@toc',
          to: function () {

            if (!grunt.file.exists('csstoc.json')) {
              return '';
            }

            var toc_file = grunt.file.readJSON('csstoc.json')
            var files = toc_file.results;
            var toc = '';
            var i = 1;
            var match;

            function capitalize(s) {
              return s[0].toUpperCase() + s.slice(1);
            }

            for (var key in files) {
              if (files.hasOwnProperty(key)) {

                var results = files[key];

                for (var key in results) {
                  if (results.hasOwnProperty(key)) {

                    match = results[key]['match'];
                    match = match.replace(/"|'|@import|;|.scss|.less/gi, "").trim();
                    match = match.split('/').pop();
                    match = capitalize(match);

                    if (match !== 'Variables' && match !== 'Mixins') {
                      toc += '\n    ' + i + '. ' + match;
                      i++;
                    }
                  }
                }
              }
            }
            return toc;
          }
        },
        // Add empty line after section & subsection comment
        {
          from: /=== \*\//g,
          to: '=== */\n'
        },
        // Add empty line after rule if it doesn't have one already
        {
          from: /}(?!\n\n)/gi,
          to: '}\n'
        }]
      },

      js: {
        src: ['<%%= xh.dist %>/js/main.js'],
        overwrite: true,
        replacements: [{
          from: '@@timestamp',
          to: '<%%= grunt.template.today() %>'
        }]
      },

      xprecise: {
        src: ['<%%= xh.src %>/includes/scripts.html'],
        overwrite: true,
        replacements: [{
          from: '<script src="http://cssonsails.org/xprecise/xprecise.min.js"></script>',
          to: ''
        }]
      }
    },

    // Create list of @imports
    search: {
      imports: {
        files: {<% if (cssPreprocessor === 'SCSS') { %>
            src: ['<%%= xh.src %>/scss/main.scss']<% } %><% if (cssPreprocessor === 'LESS') { %>
            src: ['<%%= xh.src %>/less/main.less']<% } %>
        },
        options: {
          searchString: /@import[ \("']*([^;]+)[;\)"']*/g,
          logFormat: "json",
          logFile: "csstoc.json"
        }
      }
    },

    // Watch
    watch: {
      options: {
        spawn: false
      },<% if (cssPreprocessor === 'SCSS') { %>

      scss: {
        files: ['<%%= xh.src %>/scss/*.scss'],
        tasks: ['sass', 'autoprefixer', 'cssbeautifier', 'search', 'replace:css'<% if (isWP) { %>, 'copy:wp'<% } %>]
      },<% } %><% if (cssPreprocessor === 'LESS') { %>

      less: {
        files: ['<%%= xh.src %>/less/*.less'],
        tasks: ['less', 'autoprefixer', 'cssbeautifier', 'search', 'replace:css'<% if (isWP) { %>, 'copy:wp'<% } %>]
      },<% } %>

      css: {
        files: ['<%%= xh.dist %>/css/*.css'],
        options: {
          livereload: true
        }
      },

      html: {
        files: ['<%%= xh.src %>/*.html', '<%%= xh.src %>/includes/*.html'],
        tasks: [
          'useminPrepare',
          'concat',
          'copy:backup',
          'usemin',
          'includereplace',
          'copy:restore',
          'jsbeautifier:html',<% if (isWP) { %>
          'copy:wp',<% } %>
          'clean'
        ],
        options: {
          livereload: true
        }
      },

      js: {
        files: '<%%= xh.src %>/js/*.js',
        tasks: ['copy:js', 'jsbeautifier:js', 'replace:js', 'jshint'<% if (isWP) { %>, 'copy:wp'<% } %>],
        options: {
          livereload: true
        }
      }
    }

  });

  grunt.registerTask('default', [
    'clean:dist',

    // HTML
    'useminPrepare',
    'concat',
    'copy:backup',
    'usemin',
    'includereplace',
    'copy:restore',
    'jsbeautifier:html',
    'clean:tmp',

    // CSS
    <% if (cssPreprocessor === 'SCSS') { %>
    'sass',<% } %><% if (cssPreprocessor === 'LESS') { %>
    'less',<% } %>
    'autoprefixer',
    'cssbeautifier',

    // JS
    'copy:js',
    'jsbeautifier:js',

    // Replacements
    'search',
    'replace:css',
    'replace:js',<% if (isWP) { %>

    'copy:wp',<% } %>

    // Checks
    'jshint'
  ]);

  grunt.registerTask('validate', [
    'validation'
  ]);

  grunt.registerTask('qa', [
    'replace:xprecise',
    'default',
    'validate',
    'jshint'
  ]);

  grunt.registerTask('postinstall', [<% if (!useBootstrap) { %>
    'copy:normalize',<% } %><% if (useModernizr) { %>
    'uglify:modernizr',<% } %>
    'copy:jquery'
  ]);
};

<|endoftext|>