<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import scrollparent from 'scrollparent';
import scrollMonitor from 'scrollmonitor';
import ReactJsonSchema from './utils/schema2element';
import entries from 'object.entries';
import values from 'object.values';
import { generatePlaceholder } from './components/placeholder';
import AuthorTool from './components/author-tool';
import { getChildren } from 'idyll-ast';
import equal from 'fast-deep-equal';

import * as layouts from 'idyll-layouts';
import * as themes from 'idyll-themes';

import {
  getData,
  getVars,
  filterASTForDocument,
  splitAST,
  translate,
  findWrapTargets,
  filterIdyllProps,
  mapTree,
  evalExpression,
  hooks,
  scrollMonitorEvents
} from './utils';

const updatePropsCallbacks = [];
const updateRefsCallbacks = [];
const scrollWatchers = [];
const scrollOffsets = {};
const refCache = {};
const evalContext = {};
let scrollContainer;

const getLayout = layout => {
  return layouts[layout.trim()] || {};
};

const getTheme = theme => {
  return themes[theme.trim()] || {};
};

const getRefs = () => {
  const refs = {};
  if (!scrollContainer) {
    return refCache;
  }

  scrollWatchers.forEach(watcher => {
    // left and right props assume no horizontal scrolling
    const {
      watchItem,
      callbacks,
      container,
      recalculateLocation,
      offsets,
      ...watcherProps
    } = watcher;
    refs[watchItem.dataset.ref] = {
      ...watcherProps,
      ...refCache[watchItem.dataset.ref],
      domNode: watchItem
    };
  });

  return { ...refCache, ...refs };
};

let wrapperKey = 0;
const createWrapper = ({
  theme,
  layout,
  authorView,
  userViewComponent,
  userInlineViewComponent
}) => {
  return class Wrapper extends React.PureComponent {
    constructor(props) {
      super(props);

      this.key = props.idyllASTNode.id || wrapperKey++;
      this.ref = {};
      this.onUpdateRefs = this.onUpdateRefs.bind(this);
      this.onUpdateProps = this.onUpdateProps.bind(this);

      const vars = values(props.__vars__);
      const exps = values(props.__expr__);

      this.usesRefs = exps.some(v => v.includes('refs.'));

      this.state = { hasError: false, error: null };

      // listen for props updates IF we care about them
      if (vars.length || exps.length) {
        // called with new doc state
        // when any component calls updateProps()
        updatePropsCallbacks.push(this.onUpdateProps);
        this.state = this.onUpdateProps(
          props.initialState,
          Object.keys(props),
          true
        );
      }

      // listen for ref updates IF we care about them
      if (props.hasHook || this.usesRefs) {
        updateRefsCallbacks.push(this.onUpdateRefs);
      }
    }

    componentDidCatch(error, info) {
      this.setState({ hasError: true, error: error });
    }

    onUpdateProps(newState, changedKeys, initialRender) {
      const { __vars__, __expr__ } = this.props;

      // were there changes to any vars we track?
      // or vars our expressions reference?
      const shouldUpdate =
        initialRender ||
        changedKeys.some(k => {
          return (
            values(__vars__).includes(k) ||
            values(__expr__).some(expr => expr.includes(k))
          );
        });
      // if nothing we care about changed bail out and don't re-render
      if (!shouldUpdate) return;

      // update this component's state
      const nextState = {};
      // pull in the latest value for any tracked vars
      Object.keys(__vars__).forEach(key => {
        nextState[key] = newState[__vars__[key]];
      });
      // re-run this component's expressions using the latest doc state
      Object.keys(__expr__).forEach(key => {
        nextState[key] = evalExpression(
          { ...newState, refs: getRefs() },
          __expr__[key],
          key,
          evalContext
        );
      });

      if (initialRender) {
        return Object.assign({ hasError: false }, nextState);
      }
      // trigger a re-render of this component
      // and more importantly, its wrapped component
      this.setState(Object.assign({ hasError: false, error: null }, nextState));
    }

    onUpdateRefs(newState) {
      const { __expr__ } = this.props;

      if (this.usesRefs) {
        const nextState = { refs: newState.refs };
        entries(__expr__).forEach(([key, val]) => {
          if (!val.includes('refs.')) {
            return;
          }
          nextState[key] = evalExpression(newState, val, key, evalContext);
        });

        // trigger a render with latest state
        this.setState(nextState);
      }
    }

    componentWillUnmount() {
      const propsIndex = updatePropsCallbacks.indexOf(this.onUpdateProps);
      if (propsIndex > -1) updatePropsCallbacks.splice(propsIndex, 1);

      const refsIndex = updateRefsCallbacks.indexOf(this.onUpdateRefs);
      if (refsIndex > -1) updateRefsCallbacks.splice(refsIndex, 1);
    }

    render() {
      const state = filterIdyllProps(this.state, this.props.isHTMLNode);
      const { children, ...passThruProps } = filterIdyllProps(
        this.props,
        this.props.isHTMLNode
      );
      let childComponent = null;
      let uniqueKey = `${this.key}-help`;
      let returnComponent = React.Children.map(children, (c, i) => {
        childComponent = c;
        return React.cloneElement(c, {
          key: `${this.key}-${i}`,
          idyll: {
            theme: getTheme(theme),
            layout: getLayout(layout),
            authorView: authorView
          },
          ...state,
          ...passThruProps
        });
      });
      if (this.state.hasError) {
        returnComponent = (

            {this.state.error.message}

        );
      }
      const metaData = childComponent.type._idyll;
      if (authorView && metaData && metaData.props) {
        // ensure inline elements do not have this overlay
        if (
          metaData.displayType === undefined ||
          metaData.displayType !== 'inline'
        ) {
          const ViewComponent = userViewComponent || AuthorTool;
          return (
            <ViewComponent
              idyllASTNode={this.props.idyllASTNode}
              component={returnComponent}
              authorComponent={childComponent}
              uniqueKey={uniqueKey}
            />
          );
        } else if (metaData.displayType === 'inline') {
          const InlineViewComponent =
            userInlineViewComponent || userViewComponent || AuthorTool;
          return (
            <InlineViewComponent
              idyllASTNode={this.props.idyllASTNode}
              component={returnComponent}
              authorComponent={childComponent}
              uniqueKey={uniqueKey}
            />
          );
        }
      }
      return returnComponent;
    }
  };
};

const getDerivedValues = dVars => {
  const o = {};
  Object.keys(dVars).forEach(key => (o[key] = dVars[key].value));
  return o;
};

class IdyllRuntime extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {};
    this.scrollListener = this.scrollListener.bind(this);
    this.initScrollListener = this.initScrollListener.bind(this);
    const ast = filterASTForDocument(props.ast);

    const { vars, derived, data, elements } = splitAST(getChildren(ast));
    const Wrapper = createWrapper({
      theme: props.theme,
      layout: props.layout,
      authorView: props.authorView,
      userViewComponent: props.userViewComponent,
      userInlineViewComponent: props.userInlineViewComponent
    });

    let hasInitialized = false;
    let initialContext = {};
    // Initialize a custom context
    let _initializeCallbacks = [];
    let _mountCallbacks = [];
    let _updateCallbacks = [];

    (this._onInitializeState = () => {
      _initializeCallbacks.forEach(cb => {
        cb();
      });
    }),
      (this._onMount = () => {
        _mountCallbacks.forEach(cb => {
          cb();
        });
      }),
      (this._onUpdateState = newData => {
        _updateCallbacks.forEach(cb => {
          cb(newData);
        });
      });
    if (typeof props.context === 'function') {
      props.context({
        update: newState => {
          if (!hasInitialized) {
            initialContext = Object.assign(initialContext, newState);
          } else {
            this.updateState(newState);
          }
        },
        data: () => {
          return this.state;
        },
        onInitialize: cb => {
          _initializeCallbacks.push(cb);
        },
        onMount: cb => {
          _mountCallbacks.push(cb);
        },
        onUpdate: cb => {
          _updateCallbacks.push(cb);
        }
      });
    }

    const dataStore = getData(data, props.datasets);
    const initialState = Object.assign(
      {},
      {
        ...getVars(vars, initialContext),
        ...dataStore.syncData
      },
      initialContext,
      props.initialState ? props.initialState : {}
    );

    const { asyncData: asyncDataStore } = dataStore;
    const asyncDataStoreKeys = Object.keys(asyncDataStore);
    asyncDataStoreKeys.forEach(key => {
      this.state[key] = asyncDataStore[key].initialValue;
    });

    asyncDataStoreKeys.map(key => {
      asyncDataStore[key].dataPromise
        .then(res => {
          this.updateState({
            ...this.state,
            [key]: res
          });
        })
        .catch(e => console.error('Error while resolving the data' + e));
    });
    const derivedVars = (this.derivedVars = getVars(derived, initialState));

    let state = (this.state = {
      ...this.state,
      ...initialState,
      ...getDerivedValues(derivedVars)
    });

    this.updateState = newState => {
      // merge new doc state with old
      const newMergedState = { ...this.state, ...newState };
      // update derived values
      const newDerivedValues = getDerivedValues(
        getVars(derived, newMergedState)
      );
      const nextState = { ...newMergedState, ...newDerivedValues };

      const changedMap = {};
      const changedKeys = Object.keys(state).reduce((acc, k) => {
        if (!equal(state[k], nextState[k])) {
          acc.push(k);
          changedMap[k] = nextState[k] || state[k];
        }
        return acc;
      }, []);

      // Update doc state reference.
      // We re-use the same object here so that
      // IdyllRuntime.state can be accurately checked in tests
      state = Object.assign(state, nextState);
      // pass the new doc state to all listeners aka component wrappers
      updatePropsCallbacks.forEach(f => f(state, changedKeys));

      changedKeys.length &&
        this._onUpdateState &&
        this._onUpdateState(changedMap);
    };

    evalContext.__idyllUpdate = this.updateState;
    hasInitialized = true;
    this._onInitializeState && this._onInitializeState();

    // Put these in to avoid hard errors if people are on the latest
    // CLI but haven't updated their local default components
    const fallbackComponents = {
      'text-container': generatePlaceholder('TextContainer'),
      'full-width': generatePlaceholder('FullWidth')
    };

    // Components that the Document needs to function properly
    const internalComponents = {
      Wrapper
    };

    Object.keys(internalComponents).forEach(key => {
      if (props.components[key]) {
        console.warn(
          `Warning! You are including a component named ${key}, but this is a reserved Idyll component. Please rename your component.`
        );
      }
    });

    const components = Object.assign(
      fallbackComponents,
      props.components,
      internalComponents
    );

    const rjs = new ReactJsonSchema(components);
    const schema = translate(ast);
    const wrapTargets = findWrapTargets(schema, this.state, props.components);
    let refCounter = 0;
    const transformedSchema = mapTree(schema, node => {
      // console.log('mapoing ', node.component || node.type);
      if (!node.component) {
        if (node.type && node.type === 'textnode') return node.value;
      }

      // transform refs from strings to functions and store them
      if (node.ref || node.hasHook) {
        node.refName = node.ref || node.component + (refCounter++).toString();
        node.ref = el => {
          if (!el) return;
          const domNode = ReactDOM.findDOMNode(el);
          domNode.dataset.ref = node.refName;
          scrollOffsets[node.refName] = node.scrollOffset || 0;
          refCache[node.refName] = {
            props: node,
            domNode: domNode,
            component: el
          };
        };
        refCache[node.refName] = {
          props: node,
          domNode: null
        };
      }
      //Inspect for isHTMLNode  props and to check for dynamic components.
      if (!wrapTargets.includes(node)) {
        // console.log('node not wrapped', node);

        if (
          this.props.wrapTextComponents.indexOf(node.component) > -1 &&
          this.props.textEditComponent
        ) {
          const { idyllASTNode, ...rest } = node;
          return {
            component: this.props.textEditComponent,
            idyllASTNode: idyllASTNode,
            children: [rest]
          };
        }

        // Don't include the AST node reference on unwrapped components
        const { idyllASTNode, ...rest } = node;
        return rest;
      }
      const {
        component,
        children,
        idyllASTNode,
        key,
        __vars__ = {},
        __expr__ = {},
        ...props // actual component props
      } = node;

      // assign the initial values for tracked vars and expressions
      Object.keys(props).forEach(k => {
        if (__vars__[k]) {
          node[k] = state[__vars__[k]];
        }
        if (__expr__[k] !== undefined) {
          if (hooks.indexOf(k) > -1) {
            return;
          }
          node[k] = evalExpression(
            { ...state, refs: getRefs() },
            __expr__[k],
            k,
            evalContext
          );
        }
      });
      const resolvedComponent = rjs.resolveComponent(node);
      const isHTMLNode = typeof resolvedComponent === 'string';

      return {
        component: Wrapper,
        __vars__,
        __expr__,
        idyllASTNode,
        isHTMLNode: isHTMLNode,
        hasHook: node.hasHook,
        refName: node.refName,
        initialState: this.state,
        updateProps: newProps => {
          // init new doc state object
          const newState = {};
          // iterate over passed in updates
          Object.keys(newProps).forEach(k => {
            // if a tracked var was updated get its new value
            if (__vars__[k]) {
              newState[__vars__[k]] = newProps[k];
            }
          });
          this.updateState(newState);
        },
        children: [filterIdyllProps(node, isHTMLNode)]
      };
    });

    this.kids = rjs.parseSchema(transformedSchema);
  }

  scrollListener() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
  }

  initScrollListener(el) {
    if (!el) return;

    let scroller = scrollparent(el);
    if (
      scroller === document.documentElement ||
      scroller === document.body ||
      scroller === window
    ) {
      scroller = window;
      scrollContainer = scrollMonitor;
    } else {
      scrollContainer = scrollMonitor.createContainer(scroller);
    }
    Object.keys(refCache).forEach(key => {
      const { props, domNode } = refCache[key];
      const watcher = scrollContainer.create(domNode, scrollOffsets[key]);
      hooks.forEach(hook => {
        if (props[hook]) {
          watcher[scrollMonitorEvents[hook]](() => {
            evalExpression(
              { ...this.state, refs: getRefs() },
              props[hook],
              hook,
              evalContext
            )();
          });
        }
      });
      scrollWatchers.push(watcher);
    });
    scroller.addEventListener('scroll', this.scrollListener);
  }

  updateDerivedVars(newState) {
    const context = {};
    Object.keys(this.derivedVars).forEach(dv => {
      this.derivedVars[dv].value = this.derivedVars[dv].update(
        newState,
        this.state,
        context
      );
      context[dv] = this.derivedVars[dv].value;
    });
  }

  getDerivedVars() {
    let dvs = {};
    Object.keys(this.derivedVars).forEach(dv => {
      dvs[dv] = this.derivedVars[dv].value;
    });
    return dvs;
  }

  componentDidMount() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
    this._onMount && this._onMount();
  }

  render() {
    return (

        {this.kids}

    );
  }
}

IdyllRuntime.defaultProps = {
  layout: 'blog',
  theme: 'github',
  authorView: false,
  insertStyles: false,
  wrapTextComponents: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'CodeHighlight']
};

export default IdyllRuntime;
<commit_msg>prefer local vars over props<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import scrollparent from 'scrollparent';
import scrollMonitor from 'scrollmonitor';
import ReactJsonSchema from './utils/schema2element';
import entries from 'object.entries';
import values from 'object.values';
import { generatePlaceholder } from './components/placeholder';
import AuthorTool from './components/author-tool';
import { getChildren } from 'idyll-ast';
import equal from 'fast-deep-equal';

import * as layouts from 'idyll-layouts';
import * as themes from 'idyll-themes';

import {
  getData,
  getVars,
  filterASTForDocument,
  splitAST,
  translate,
  findWrapTargets,
  filterIdyllProps,
  mapTree,
  evalExpression,
  hooks,
  scrollMonitorEvents
} from './utils';

const updatePropsCallbacks = [];
const updateRefsCallbacks = [];
const scrollWatchers = [];
const scrollOffsets = {};
const refCache = {};
const evalContext = {};
let scrollContainer;

const getLayout = layout => {
  return layouts[layout.trim()] || {};
};

const getTheme = theme => {
  return themes[theme.trim()] || {};
};

const getRefs = () => {
  const refs = {};
  if (!scrollContainer) {
    return refCache;
  }

  scrollWatchers.forEach(watcher => {
    // left and right props assume no horizontal scrolling
    const {
      watchItem,
      callbacks,
      container,
      recalculateLocation,
      offsets,
      ...watcherProps
    } = watcher;
    refs[watchItem.dataset.ref] = {
      ...watcherProps,
      ...refCache[watchItem.dataset.ref],
      domNode: watchItem
    };
  });

  return { ...refCache, ...refs };
};

let wrapperKey = 0;
const createWrapper = ({
  theme,
  layout,
  authorView,
  userViewComponent,
  userInlineViewComponent
}) => {
  return class Wrapper extends React.PureComponent {
    constructor(props) {
      super(props);

      this.key = props.idyllASTNode.id || wrapperKey++;
      this.ref = {};
      this.onUpdateRefs = this.onUpdateRefs.bind(this);
      this.onUpdateProps = this.onUpdateProps.bind(this);

      const vars = values(props.__vars__);
      const exps = values(props.__expr__);

      this.usesRefs = exps.some(v => v.includes('refs.'));

      this.state = { hasError: false, error: null };

      // listen for props updates IF we care about them
      if (vars.length || exps.length) {
        // called with new doc state
        // when any component calls updateProps()
        updatePropsCallbacks.push(this.onUpdateProps);
        this.state = this.onUpdateProps(
          props.initialState,
          Object.keys(props),
          true
        );
      }

      // listen for ref updates IF we care about them
      if (props.hasHook || this.usesRefs) {
        updateRefsCallbacks.push(this.onUpdateRefs);
      }
    }

    componentDidCatch(error, info) {
      this.setState({ hasError: true, error: error });
    }

    onUpdateProps(newState, changedKeys, initialRender) {
      const { __vars__, __expr__ } = this.props;

      // were there changes to any vars we track?
      // or vars our expressions reference?
      const shouldUpdate =
        initialRender ||
        changedKeys.some(k => {
          return (
            values(__vars__).includes(k) ||
            values(__expr__).some(expr => expr.includes(k))
          );
        });
      // if nothing we care about changed bail out and don't re-render
      if (!shouldUpdate) return;

      // update this component's state
      const nextState = {};
      // pull in the latest value for any tracked vars
      Object.keys(__vars__).forEach(key => {
        nextState[key] = newState[__vars__[key]];
      });
      // re-run this component's expressions using the latest doc state
      Object.keys(__expr__).forEach(key => {
        nextState[key] = evalExpression(
          { ...newState, refs: getRefs() },
          __expr__[key],
          key,
          evalContext
        );
      });

      if (initialRender) {
        return Object.assign({ hasError: false }, nextState);
      }
      // trigger a re-render of this component
      // and more importantly, its wrapped component
      this.setState(Object.assign({ hasError: false, error: null }, nextState));
    }

    onUpdateRefs(newState) {
      const { __expr__ } = this.props;

      if (this.usesRefs) {
        const nextState = { refs: newState.refs };
        entries(__expr__).forEach(([key, val]) => {
          if (!val.includes('refs.')) {
            return;
          }
          nextState[key] = evalExpression(newState, val, key, evalContext);
        });

        // trigger a render with latest state
        this.setState(nextState);
      }
    }

    componentWillUnmount() {
      const propsIndex = updatePropsCallbacks.indexOf(this.onUpdateProps);
      if (propsIndex > -1) updatePropsCallbacks.splice(propsIndex, 1);

      const refsIndex = updateRefsCallbacks.indexOf(this.onUpdateRefs);
      if (refsIndex > -1) updateRefsCallbacks.splice(refsIndex, 1);
    }

    render() {
      const state = filterIdyllProps(this.state, this.props.isHTMLNode);
      const { children, ...passThruProps } = filterIdyllProps(
        this.props,
        this.props.isHTMLNode
      );
      let childComponent = null;
      let uniqueKey = `${this.key}-help`;
      let returnComponent = React.Children.map(children, (c, i) => {
        childComponent = c;
        return React.cloneElement(c, {
          key: `${this.key}-${i}`,
          idyll: {
            theme: getTheme(theme),
            layout: getLayout(layout),
            authorView: authorView
          },
          ...state,
          ...passThruProps
        });
      });
      if (this.state.hasError) {
        returnComponent = (

            {this.state.error.message}

        );
      }
      const metaData = childComponent.type._idyll;
      if (authorView && metaData && metaData.props) {
        // ensure inline elements do not have this overlay
        if (
          metaData.displayType === undefined ||
          metaData.displayType !== 'inline'
        ) {
          const ViewComponent = userViewComponent || AuthorTool;
          return (
            <ViewComponent
              idyllASTNode={this.props.idyllASTNode}
              component={returnComponent}
              authorComponent={childComponent}
              uniqueKey={uniqueKey}
            />
          );
        } else if (metaData.displayType === 'inline') {
          const InlineViewComponent =
            userInlineViewComponent || userViewComponent || AuthorTool;
          return (
            <InlineViewComponent
              idyllASTNode={this.props.idyllASTNode}
              component={returnComponent}
              authorComponent={childComponent}
              uniqueKey={uniqueKey}
            />
          );
        }
      }
      return returnComponent;
    }
  };
};

const getDerivedValues = dVars => {
  const o = {};
  Object.keys(dVars).forEach(key => (o[key] = dVars[key].value));
  return o;
};

class IdyllRuntime extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {};
    this.scrollListener = this.scrollListener.bind(this);
    this.initScrollListener = this.initScrollListener.bind(this);
    const ast = filterASTForDocument(props.ast);

    const { vars, derived, data, elements } = splitAST(getChildren(ast));
    const Wrapper = createWrapper({
      theme: props.theme,
      layout: props.layout,
      authorView: props.authorView,
      userViewComponent: props.userViewComponent,
      userInlineViewComponent: props.userInlineViewComponent
    });

    let hasInitialized = false;
    let initialContext = {};
    // Initialize a custom context
    let _initializeCallbacks = [];
    let _mountCallbacks = [];
    let _updateCallbacks = [];

    this._onInitializeState = () => {
      _initializeCallbacks.forEach(cb => {
        cb();
      });
    };
    this._onMount = () => {
      _mountCallbacks.forEach(cb => {
        cb();
      });
    };
    this._onUpdateState = newData => {
      _updateCallbacks.forEach(cb => {
        cb(newData);
      });
    };
    if (typeof props.context === 'function') {
      props.context({
        update: newState => {
          if (!hasInitialized) {
            initialContext = Object.assign(initialContext, newState);
          } else {
            this.updateState(newState);
          }
        },
        data: () => {
          return this.state;
        },
        onInitialize: cb => {
          _initializeCallbacks.push(cb);
        },
        onMount: cb => {
          _mountCallbacks.push(cb);
        },
        onUpdate: cb => {
          _updateCallbacks.push(cb);
        }
      });
    }

    const dataStore = getData(data, props.datasets);
    const initialState = Object.assign(
      {},
      {
        ...getVars(vars, initialContext),
        ...dataStore.syncData
      },
      initialContext,
      props.initialState ? props.initialState : {}
    );

    const { asyncData: asyncDataStore } = dataStore;
    const asyncDataStoreKeys = Object.keys(asyncDataStore);
    asyncDataStoreKeys.forEach(key => {
      this.state[key] = asyncDataStore[key].initialValue;
    });

    asyncDataStoreKeys.map(key => {
      asyncDataStore[key].dataPromise
        .then(res => {
          this.updateState({
            ...this.state,
            [key]: res
          });
        })
        .catch(e => console.error('Error while resolving the data' + e));
    });
    const derivedVars = (this.derivedVars = getVars(derived, initialState));

    let state = (this.state = {
      ...this.state,
      ...initialState,
      ...getDerivedValues(derivedVars)
    });

    this.updateState = newState => {
      // merge new doc state with old
      const newMergedState = { ...this.state, ...newState };
      // update derived values
      const newDerivedValues = getDerivedValues(
        getVars(derived, newMergedState)
      );
      const nextState = { ...newMergedState, ...newDerivedValues };

      const changedMap = {};
      const changedKeys = Object.keys(state).reduce((acc, k) => {
        if (!equal(state[k], nextState[k])) {
          acc.push(k);
          changedMap[k] = nextState[k] || state[k];
        }
        return acc;
      }, []);

      // Update doc state reference.
      // We re-use the same object here so that
      // IdyllRuntime.state can be accurately checked in tests
      state = Object.assign(state, nextState);
      // pass the new doc state to all listeners aka component wrappers
      updatePropsCallbacks.forEach(f => f(state, changedKeys));

      changedKeys.length &&
        this._onUpdateState &&
        this._onUpdateState(changedMap);
    };

    evalContext.__idyllUpdate = this.updateState;
    hasInitialized = true;
    this._onInitializeState && this._onInitializeState();

    // Put these in to avoid hard errors if people are on the latest
    // CLI but haven't updated their local default components
    const fallbackComponents = {
      'text-container': generatePlaceholder('TextContainer'),
      'full-width': generatePlaceholder('FullWidth')
    };

    // Components that the Document needs to function properly
    const internalComponents = {
      Wrapper
    };

    Object.keys(internalComponents).forEach(key => {
      if (props.components[key]) {
        console.warn(
          `Warning! You are including a component named ${key}, but this is a reserved Idyll component. Please rename your component.`
        );
      }
    });

    const components = Object.assign(
      fallbackComponents,
      props.components,
      internalComponents
    );

    const rjs = new ReactJsonSchema(components);
    const schema = translate(ast);
    const wrapTargets = findWrapTargets(schema, this.state, props.components);
    let refCounter = 0;
    const transformedSchema = mapTree(schema, node => {
      // console.log('mapoing ', node.component || node.type);
      if (!node.component) {
        if (node.type && node.type === 'textnode') return node.value;
      }

      // transform refs from strings to functions and store them
      if (node.ref || node.hasHook) {
        node.refName = node.ref || node.component + (refCounter++).toString();
        node.ref = el => {
          if (!el) return;
          const domNode = ReactDOM.findDOMNode(el);
          domNode.dataset.ref = node.refName;
          scrollOffsets[node.refName] = node.scrollOffset || 0;
          refCache[node.refName] = {
            props: node,
            domNode: domNode,
            component: el
          };
        };
        refCache[node.refName] = {
          props: node,
          domNode: null
        };
      }
      //Inspect for isHTMLNode  props and to check for dynamic components.
      if (!wrapTargets.includes(node)) {
        // console.log('node not wrapped', node);

        if (
          this.props.wrapTextComponents.indexOf(node.component) > -1 &&
          this.props.textEditComponent
        ) {
          const { idyllASTNode, ...rest } = node;
          return {
            component: this.props.textEditComponent,
            idyllASTNode: idyllASTNode,
            children: [rest]
          };
        }

        // Don't include the AST node reference on unwrapped components
        const { idyllASTNode, ...rest } = node;
        return rest;
      }
      const {
        component,
        children,
        idyllASTNode,
        key,
        __vars__ = {},
        __expr__ = {},
        ...props // actual component props
      } = node;

      // assign the initial values for tracked vars and expressions
      Object.keys(props).forEach(k => {
        if (__vars__[k]) {
          node[k] = state[__vars__[k]];
        }
        if (__expr__[k] !== undefined) {
          if (hooks.indexOf(k) > -1) {
            return;
          }
          node[k] = evalExpression(
            { ...state, refs: getRefs() },
            __expr__[k],
            k,
            evalContext
          );
        }
      });
      const resolvedComponent = rjs.resolveComponent(node);
      const isHTMLNode = typeof resolvedComponent === 'string';

      return {
        component: Wrapper,
        __vars__,
        __expr__,
        idyllASTNode,
        isHTMLNode: isHTMLNode,
        hasHook: node.hasHook,
        refName: node.refName,
        initialState: this.state,
        updateProps: newProps => {
          // init new doc state object
          const newState = {};
          // iterate over passed in updates
          Object.keys(newProps).forEach(k => {
            // if a tracked var was updated get its new value
            if (__vars__[k]) {
              newState[__vars__[k]] = newProps[k];
            }
          });
          this.updateState(newState);
        },
        children: [filterIdyllProps(node, isHTMLNode)]
      };
    });

    this.kids = rjs.parseSchema(transformedSchema);
  }

  scrollListener() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
  }

  initScrollListener(el) {
    if (!el) return;

    let scroller = scrollparent(el);
    if (
      scroller === document.documentElement ||
      scroller === document.body ||
      scroller === window
    ) {
      scroller = window;
      scrollContainer = scrollMonitor;
    } else {
      scrollContainer = scrollMonitor.createContainer(scroller);
    }
    Object.keys(refCache).forEach(key => {
      const { props, domNode } = refCache[key];
      const watcher = scrollContainer.create(domNode, scrollOffsets[key]);
      hooks.forEach(hook => {
        if (props[hook]) {
          watcher[scrollMonitorEvents[hook]](() => {
            evalExpression(
              { ...this.state, refs: getRefs() },
              props[hook],
              hook,
              evalContext
            )();
          });
        }
      });
      scrollWatchers.push(watcher);
    });
    scroller.addEventListener('scroll', this.scrollListener);
  }

  updateDerivedVars(newState) {
    const context = {};
    Object.keys(this.derivedVars).forEach(dv => {
      this.derivedVars[dv].value = this.derivedVars[dv].update(
        newState,
        this.state,
        context
      );
      context[dv] = this.derivedVars[dv].value;
    });
  }

  getDerivedVars() {
    let dvs = {};
    Object.keys(this.derivedVars).forEach(dv => {
      dvs[dv] = this.derivedVars[dv].value;
    });
    return dvs;
  }

  componentDidMount() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
    this._onMount && this._onMount();
  }

  render() {
    return (

        {this.kids}

    );
  }
}

IdyllRuntime.defaultProps = {
  layout: 'blog',
  theme: 'github',
  authorView: false,
  insertStyles: false,
  wrapTextComponents: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'CodeHighlight']
};

export default IdyllRuntime;
<|endoftext|>


<commit_before>'use strict';

function setElementsValueByName(name, value) {
  var elements = document.getElementsByName(name);
  for(var i = 0; i < elements.length; i++) {
    elements[i].value = value;
  }
}

function serializeForms() {
  var fields = [];
  for(var i = 0; i < document.forms.length; i++) {
    var elements = document.forms[i].elements;
    for(var j = 0; j < elements.length; j++) {
      var el = elements[j];
      if(el.clientHeight < 1 || el.clientWidth < 1) {
        continue;
      }
      fields.push({name: el.name, value: el.value});
    }
  }
  return fields;
}

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
  switch(message.type) {
  case "SET_FORM":
    message.data.fields.forEach(function(field) {
      setElementsValueByName(field.name, field.value);
    });
    break;
  case "GET_FORM":
    sendResponse(serializeForms());
    break;
  }
});
<commit_msg>Stop serialising fields with no name<commit_after>'use strict';

function setElementsValueByName(name, value) {
  var elements = document.getElementsByName(name);
  for(var i = 0; i < elements.length; i++) {
    elements[i].value = value;
  }
}

function serializeForms() {
  var fields = [];
  for(var i = 0; i < document.forms.length; i++) {
    var elements = document.forms[i].elements;
    for(var j = 0; j < elements.length; j++) {
      var el = elements[j];
      if(!el.name || el.clientHeight < 1 || el.clientWidth < 1) {
        continue;
      }
      fields.push({name: el.name, value: el.value});
    }
  }
  return fields;
}

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
  switch(message.type) {
  case "SET_FORM":
    message.data.fields.forEach(function(field) {
      setElementsValueByName(field.name, field.value);
    });
    break;
  case "GET_FORM":
    sendResponse(serializeForms());
    break;
  }
});
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import scrollparent from 'scrollparent';
import scrollMonitor from 'scrollmonitor';
import ReactJsonSchema from './utils/schema2element';
import entries from 'object.entries';
import values from 'object.values';
import { generatePlaceholder } from './components/placeholder';
import Overlay from './components/overlay';
import ReactTooltip from 'react-tooltip';

import * as layouts from 'idyll-layouts';
import * as themes from 'idyll-themes';

import {
  getData,
  getVars,
  filterASTForDocument,
  splitAST,
  translate,
  findWrapTargets,
  filterIdyllProps,
  mapTree,
  evalExpression,
  hooks,
  scrollMonitorEvents
} from './utils';

const updatePropsCallbacks = [];
const updateRefsCallbacks = [];
const scrollWatchers = [];
const scrollOffsets = {};
const refCache = {};
const evalContext = {};
let scrollContainer;

const getLayout = (layout) => {
  return layouts[layout.trim()] || {};
}

const getTheme = (theme) => {
  return themes[theme.trim()] || {};
}

const getRefs = () => {
  const refs = {};
  if (!scrollContainer) {
    return;
  }

  scrollWatchers.forEach(watcher => {
    // left and right props assume no horizontal scrolling
    const { watchItem, callbacks, container, recalculateLocation, offsets, ...watcherProps} = watcher;
    refs[watchItem.dataset.ref] = {
      ...watcherProps,
      domNode: watchItem
    };
  });

  return refs;
};

let wrapperKey = 0;
const createWrapper = ({ theme, layout }) => {
  return class Wrapper extends React.PureComponent {
  constructor(props) {
    super(props);

    this.key = wrapperKey++;
    this.ref = {};
    this.onUpdateRefs = this.onUpdateRefs.bind(this);
    this.onUpdateProps = this.onUpdateProps.bind(this);

    const vars = values(props.__vars__);
    const exps = values(props.__expr__);

    this.usesRefs = exps.some(v => v.includes('refs.'));

    // listen for props updates IF we care about them
    if (vars.length || exps.length) {
      // called with new doc state
      // when any component calls updateProps()
      updatePropsCallbacks.push(this.onUpdateProps);
    }

    // listen for ref updates IF we care about them
    if (props.hasHook || this.usesRefs) {
      updateRefsCallbacks.push(this.onUpdateRefs);
    }

    this.state = { hasError: false, error: null };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true, error: error });
  }

  onUpdateProps(newState, changedKeys) {
    const { __vars__, __expr__ } = this.props;

    // were there changes to any vars we track?
    // or vars our expressions reference?
    const shouldUpdate = changedKeys.some(k => {
      return (
        values(__vars__).includes(k) ||
        values(__expr__).some(expr => expr.includes(k))
      );
    });
    // if nothing we care about changed bail out and don't re-render
    if (!shouldUpdate) return;

    // update this component's state
    const nextState = {};
    // pull in the latest value for any tracked vars
    Object.keys(__vars__).forEach(key => {
      nextState[key] = newState[__vars__[key]];
    });
    // re-run this component's expressions using the latest doc state
    Object.keys(__expr__).forEach(key => {
      nextState[key] = evalExpression(newState, __expr__[key], key, evalContext);
    });
    // trigger a re-render of this component
    // and more importantly, its wrapped component
    this.setState(Object.assign({ hasError: false }, nextState));
  }

  onUpdateRefs(newState) {
    const { __expr__ } = this.props;

    if (this.usesRefs) {
      const nextState = {refs: newState.refs};
      entries(__expr__)
        .forEach(([key, val]) => {
          if (!key.includes('refs.')) {
            return;
          }
          nextState[key] = evalExpression(newState, val, key, evalContext);
        });

      // trigger a render with latest state
      this.setState(nextState);
    }
  }

  componentWillUnmount() {
    const propsIndex = updatePropsCallbacks.indexOf(this.onUpdateProps);
    if (propsIndex > -1) updatePropsCallbacks.splice(propsIndex, 1);

    const refsIndex = updateRefsCallbacks.indexOf(this.onUpdateRefs);
    if (refsIndex > -1) updateRefsCallbacks.splice(refsIndex, 1);
  }

  handleFormatComponent(info) {
    const allProps = Object.keys(info.props);
    // TODO -- should we display children prop? Probly not
    const valuesOfProps = allProps.map((prop) => {
      const val = info.props[prop];
      if (val) {
        let propertyValue = null;
        if (val.constructor === Object) {
          propertyValue = JSON.stringify(val);
        } else {
          propertyValue = val.toString ? val.toString() : val;
        }
        return (

            {prop.toString() + ": " + propertyValue}

        )
      } else {
        return null;
      }
    });
    return (

        This component's type is {info.type.name ? info.type.name : info.type}

{valuesOfProps}

    );
  }

  render() {
    if (this.state.hasError) {
      return (

          {this.state.error.message}

      );
      }

      const state = filterIdyllProps(this.state, this.props.isHTMLNode);
      const { children, ...passThruProps } = filterIdyllProps(this.props, this.props.isHTMLNode);
      let childComponent = null;
      const returnComponent = React.Children.map(children, (c, i) => {
        childComponent = c;
        return React.cloneElement(c, {
          key: `${this.key}-${i}`,
          idyll: {
            theme: getTheme(theme),
            layout: getLayout(layout)
          },
          ...state,
          ...passThruProps,
        })
      });
      return (
        <Overlay>
          {returnComponent}
          <button className="overlay-button" data-tip data-for={`${this.key}-help`}>

          </button>
          <ReactTooltip id={`${this.key}-help`} type='error' effect='solid'>
            {this.handleFormatComponent(childComponent)}
          </ReactTooltip>
        </Overlay>
      );
    }
  }
};

const getDerivedValues = dVars => {
  const o = {};
  Object.keys(dVars).forEach(key => o[key] = dVars[key].value);
  return o;
};

class IdyllRuntime extends React.PureComponent {
  constructor(props) {
    super(props);

    this.scrollListener = this.scrollListener.bind(this);
    this.initScrollListener = this.initScrollListener.bind(this);

    const ast = filterASTForDocument(props.ast);

    const {
      vars,
      derived,
      data,
      elements,
    } = splitAST(ast);

    const Wrapper = createWrapper({ theme: props.theme, layout: props.layout });

    const initialState = Object.assign({}, {
      ...getVars(vars),
      ...getData(data, props.datasets),
    }, props.initialState ? props.initialState : {});
    const derivedVars = this.derivedVars = getVars(derived, initialState);

    let state = this.state = {
      ...initialState,
      ...getDerivedValues(derivedVars),
    };

    this.updateState = (newState) => {
      // merge new doc state with old
      const newMergedState = {...this.state, ...newState};
      // update derived values
      const newDerivedValues = getDerivedValues(
        getVars(derived, newMergedState),
      );
      const nextState = {...newMergedState, ...newDerivedValues};

      const changedMap = {};
      const changedKeys = Object.keys(state).reduce(
        (acc, k) => {
          if (state[k] !== nextState[k]) {
            acc.push(k);
            changedMap[k] = nextState[k] || state[k];
          }
          return acc;
        },
        []
      )

      // Update doc state reference.
      // We re-use the same object here so that
      // IdyllRuntime.state can be accurately checked in tests
      state = Object.assign(state, nextState);
      // pass the new doc state to all listeners aka component wrappers
      updatePropsCallbacks.forEach(f => f(state, changedKeys));

      changedKeys.length && this._onUpdateState && this._onUpdateState(changedMap);
    };

    evalContext.update = this.updateState;

    // Put these in to avoid hard errors if people are on the latest
    // CLI but haven't updated their local default components
    const fallbackComponents = {
      'text-container': generatePlaceholder('TextContainer'),
      'full-width': generatePlaceholder('FullWidth')
    };

    // Components that the Document needs to function properly
    const internalComponents = {
      Wrapper
    }

    Object.keys(internalComponents).forEach((key) => {
      if (props.components[key]) {
        console.warn(`Warning! You are including a component named ${key}, but this is a reserved Idyll component. Please rename your component.`);
      }
    })

    const components = Object.assign(fallbackComponents, props.components, internalComponents);

    const rjs = new ReactJsonSchema(components);
    const schema = translate(ast);

    const wrapTargets = findWrapTargets(schema, this.state);

    let refCounter = 0;

    const transformedSchema = mapTree(
      schema,
      node => {
        if (typeof node === 'string') return node;

        // transform refs from strings to functions and store them
        if (node.ref || node.hasHook) {
          node.refName = node.ref || node.component + (refCounter++).toString();
          node.ref = el => {
            if (!el) return;
            const domNode = ReactDOM.findDOMNode(el);
            domNode.dataset.ref = node.refName;
            scrollOffsets[node.refName] = node.scrollOffset || 0;
            refCache[node.refName] = {
              props: node,
              domNode: domNode
            };
          };
        }

        if (!wrapTargets.includes(node)) return node;

        const {
          component,
          children,
          key,
          __vars__ = {},
          __expr__ = {},
          ...props // actual component props
        } = node;

        // assign the initial values for tracked vars and expressions
        Object.keys(props).forEach(k => {
          if (__vars__[k]) {
            node[k] = state[__vars__[k]];
          }
          if (__expr__[k] && !__expr__[k].includes('refs.')) {
            if (hooks.indexOf(k) > -1) {
              return;
            }
            node[k] = evalExpression(state, __expr__[k], k, evalContext);
          }
        });

        const resolvedComponent = rjs.resolveComponent(node);
        const isHTMLNode = typeof resolvedComponent === 'string';

        return {
          component: Wrapper,
          __vars__,
          __expr__,
          isHTMLNode: isHTMLNode,
          hasHook: node.hasHook,
          refName: node.refName,
          updateProps: (newProps) => {
            // init new doc state object
            const newState = {};
            // iterate over passed in updates
            Object.keys(newProps).forEach(k => {
              // if a tracked var was updated get its new value
              if (__vars__[k]) {
                newState[__vars__[k]] = newProps[k];
              }
            });
            this.updateState(newState);
          },
          children: [
            filterIdyllProps(node, isHTMLNode)
          ],
        };
      }
    );

    this.kids = rjs.parseSchema(transformedSchema);
  }

  scrollListener() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
  }

  initScrollListener(el) {
    if (!el) return;

    let scroller = scrollparent(el);
    let scrollContainer;
    if (scroller === document.documentElement || scroller === document.body || scroller === window) {
      scroller = window;
      scrollContainer = scrollMonitor;
    } else {
      scrollContainer = scrollMonitor.createContainer(scroller);
    }
    Object.keys(refCache).forEach((key) => {
      const { props, domNode } = refCache[key];
      const watcher = scrollContainer.create(domNode, scrollOffsets[key]);
      hooks.forEach((hook) => {
        if (props[hook]) {
          watcher[scrollMonitorEvents[hook]](() => {
            evalExpression(this.state, props[hook], hook, evalContext)();
          });
        }
      })
      scrollWatchers.push(watcher);
    });
    scroller.addEventListener('scroll', this.scrollListener);
  }

  updateDerivedVars(newState) {
    Object.keys(this.derivedVars).forEach(dv => {
      this.derivedVars[dv].value = this.derivedVars[dv].update(
        newState,
        this.state,
      );
    });
  }

  getDerivedVars() {
    let dvs = {};
    Object.keys(this.derivedVars).forEach(dv => {
      dvs[dv] = this.derivedVars[dv].value;
    });
    return dvs;
  }

  componentDidMount() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));

    if (typeof this.props.context === 'function') {
      this.props.context({
        update: this.updateState.bind(this),
        data: () => {
          return this.state
        },
        onUpdate: (cb) => {
          this._onUpdateState = cb;
        }
      });
    }
  }

  render() {
    return (

        {this.kids}

    )
  }
}

IdyllRuntime.defaultProps = {
  layout: 'blog',
  theme: 'github',
  insertStyles: false
};

export default IdyllRuntime;
<commit_msg>allow 0 values and refactor and better var names<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import scrollparent from 'scrollparent';
import scrollMonitor from 'scrollmonitor';
import ReactJsonSchema from './utils/schema2element';
import entries from 'object.entries';
import values from 'object.values';
import { generatePlaceholder } from './components/placeholder';
import Overlay from './components/overlay';
import ReactTooltip from 'react-tooltip';

import * as layouts from 'idyll-layouts';
import * as themes from 'idyll-themes';

import {
  getData,
  getVars,
  filterASTForDocument,
  splitAST,
  translate,
  findWrapTargets,
  filterIdyllProps,
  mapTree,
  evalExpression,
  hooks,
  scrollMonitorEvents
} from './utils';

const updatePropsCallbacks = [];
const updateRefsCallbacks = [];
const scrollWatchers = [];
const scrollOffsets = {};
const refCache = {};
const evalContext = {};
let scrollContainer;

const getLayout = (layout) => {
  return layouts[layout.trim()] || {};
}

const getTheme = (theme) => {
  return themes[theme.trim()] || {};
}

const getRefs = () => {
  const refs = {};
  if (!scrollContainer) {
    return;
  }

  scrollWatchers.forEach(watcher => {
    // left and right props assume no horizontal scrolling
    const { watchItem, callbacks, container, recalculateLocation, offsets, ...watcherProps} = watcher;
    refs[watchItem.dataset.ref] = {
      ...watcherProps,
      domNode: watchItem
    };
  });

  return refs;
};

let wrapperKey = 0;
const createWrapper = ({ theme, layout }) => {
  return class Wrapper extends React.PureComponent {
  constructor(props) {
    super(props);

    this.key = wrapperKey++;
    this.ref = {};
    this.onUpdateRefs = this.onUpdateRefs.bind(this);
    this.onUpdateProps = this.onUpdateProps.bind(this);

    const vars = values(props.__vars__);
    const exps = values(props.__expr__);

    this.usesRefs = exps.some(v => v.includes('refs.'));

    // listen for props updates IF we care about them
    if (vars.length || exps.length) {
      // called with new doc state
      // when any component calls updateProps()
      updatePropsCallbacks.push(this.onUpdateProps);
    }

    // listen for ref updates IF we care about them
    if (props.hasHook || this.usesRefs) {
      updateRefsCallbacks.push(this.onUpdateRefs);
    }

    this.state = { hasError: false, error: null };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true, error: error });
  }

  onUpdateProps(newState, changedKeys) {
    const { __vars__, __expr__ } = this.props;

    // were there changes to any vars we track?
    // or vars our expressions reference?
    const shouldUpdate = changedKeys.some(k => {
      return (
        values(__vars__).includes(k) ||
        values(__expr__).some(expr => expr.includes(k))
      );
    });
    // if nothing we care about changed bail out and don't re-render
    if (!shouldUpdate) return;

    // update this component's state
    const nextState = {};
    // pull in the latest value for any tracked vars
    Object.keys(__vars__).forEach(key => {
      nextState[key] = newState[__vars__[key]];
    });
    // re-run this component's expressions using the latest doc state
    Object.keys(__expr__).forEach(key => {
      nextState[key] = evalExpression(newState, __expr__[key], key, evalContext);
    });
    // trigger a re-render of this component
    // and more importantly, its wrapped component
    this.setState(Object.assign({ hasError: false }, nextState));
  }

  onUpdateRefs(newState) {
    const { __expr__ } = this.props;

    if (this.usesRefs) {
      const nextState = {refs: newState.refs};
      entries(__expr__)
        .forEach(([key, val]) => {
          if (!key.includes('refs.')) {
            return;
          }
          nextState[key] = evalExpression(newState, val, key, evalContext);
        });

      // trigger a render with latest state
      this.setState(nextState);
    }
  }

  componentWillUnmount() {
    const propsIndex = updatePropsCallbacks.indexOf(this.onUpdateProps);
    if (propsIndex > -1) updatePropsCallbacks.splice(propsIndex, 1);

    const refsIndex = updateRefsCallbacks.indexOf(this.onUpdateRefs);
    if (refsIndex > -1) updateRefsCallbacks.splice(refsIndex, 1);
  }

  handleFormatComponent(info) {
    const allProps = Object.keys(info.props);
    // TODO -- should we display children prop? Probly not
    const propValues = allProps.map((prop) => {
      const propValue = info.props[prop];
      if (propValue == null) {
        return null;
      } else {
        let propValueString = null;
        if (propValue.constructor === Object) {
          propValueString = JSON.stringify(propValue);
        } else {
          propValueString = propValue.toString ? propValue.toString() : propValue;
        }
        return (

            {prop.toString() + ": " + propValueString}

        )
      }
    });
    return (

        This component's type is {info.type.name ? info.type.name : info.type}

{propValues}

    );
  }

  render() {
    if (this.state.hasError) {
      return (

          {this.state.error.message}

      );
      }

      const state = filterIdyllProps(this.state, this.props.isHTMLNode);
      const { children, ...passThruProps } = filterIdyllProps(this.props, this.props.isHTMLNode);
      let childComponent = null;
      const returnComponent = React.Children.map(children, (c, i) => {
        childComponent = c;
        return React.cloneElement(c, {
          key: `${this.key}-${i}`,
          idyll: {
            theme: getTheme(theme),
            layout: getLayout(layout)
          },
          ...state,
          ...passThruProps,
        })
      });
      return (
        <Overlay>
          {returnComponent}
          <button className="overlay-button" data-tip data-for={`${this.key}-help`}>

          </button>
          <ReactTooltip id={`${this.key}-help`} type='error' effect='solid'>
            {this.handleFormatComponent(childComponent)}
          </ReactTooltip>
        </Overlay>
      );
    }
  }
};

const getDerivedValues = dVars => {
  const o = {};
  Object.keys(dVars).forEach(key => o[key] = dVars[key].value);
  return o;
};

class IdyllRuntime extends React.PureComponent {
  constructor(props) {
    super(props);

    this.scrollListener = this.scrollListener.bind(this);
    this.initScrollListener = this.initScrollListener.bind(this);

    const ast = filterASTForDocument(props.ast);

    const {
      vars,
      derived,
      data,
      elements,
    } = splitAST(ast);

    const Wrapper = createWrapper({ theme: props.theme, layout: props.layout });

    const initialState = Object.assign({}, {
      ...getVars(vars),
      ...getData(data, props.datasets),
    }, props.initialState ? props.initialState : {});
    const derivedVars = this.derivedVars = getVars(derived, initialState);

    let state = this.state = {
      ...initialState,
      ...getDerivedValues(derivedVars),
    };

    this.updateState = (newState) => {
      // merge new doc state with old
      const newMergedState = {...this.state, ...newState};
      // update derived values
      const newDerivedValues = getDerivedValues(
        getVars(derived, newMergedState),
      );
      const nextState = {...newMergedState, ...newDerivedValues};

      const changedMap = {};
      const changedKeys = Object.keys(state).reduce(
        (acc, k) => {
          if (state[k] !== nextState[k]) {
            acc.push(k);
            changedMap[k] = nextState[k] || state[k];
          }
          return acc;
        },
        []
      )

      // Update doc state reference.
      // We re-use the same object here so that
      // IdyllRuntime.state can be accurately checked in tests
      state = Object.assign(state, nextState);
      // pass the new doc state to all listeners aka component wrappers
      updatePropsCallbacks.forEach(f => f(state, changedKeys));

      changedKeys.length && this._onUpdateState && this._onUpdateState(changedMap);
    };

    evalContext.update = this.updateState;

    // Put these in to avoid hard errors if people are on the latest
    // CLI but haven't updated their local default components
    const fallbackComponents = {
      'text-container': generatePlaceholder('TextContainer'),
      'full-width': generatePlaceholder('FullWidth')
    };

    // Components that the Document needs to function properly
    const internalComponents = {
      Wrapper
    }

    Object.keys(internalComponents).forEach((key) => {
      if (props.components[key]) {
        console.warn(`Warning! You are including a component named ${key}, but this is a reserved Idyll component. Please rename your component.`);
      }
    })

    const components = Object.assign(fallbackComponents, props.components, internalComponents);

    const rjs = new ReactJsonSchema(components);
    const schema = translate(ast);

    const wrapTargets = findWrapTargets(schema, this.state);

    let refCounter = 0;

    const transformedSchema = mapTree(
      schema,
      node => {
        if (typeof node === 'string') return node;

        // transform refs from strings to functions and store them
        if (node.ref || node.hasHook) {
          node.refName = node.ref || node.component + (refCounter++).toString();
          node.ref = el => {
            if (!el) return;
            const domNode = ReactDOM.findDOMNode(el);
            domNode.dataset.ref = node.refName;
            scrollOffsets[node.refName] = node.scrollOffset || 0;
            refCache[node.refName] = {
              props: node,
              domNode: domNode
            };
          };
        }

        if (!wrapTargets.includes(node)) return node;

        const {
          component,
          children,
          key,
          __vars__ = {},
          __expr__ = {},
          ...props // actual component props
        } = node;

        // assign the initial values for tracked vars and expressions
        Object.keys(props).forEach(k => {
          if (__vars__[k]) {
            node[k] = state[__vars__[k]];
          }
          if (__expr__[k] && !__expr__[k].includes('refs.')) {
            if (hooks.indexOf(k) > -1) {
              return;
            }
            node[k] = evalExpression(state, __expr__[k], k, evalContext);
          }
        });

        const resolvedComponent = rjs.resolveComponent(node);
        const isHTMLNode = typeof resolvedComponent === 'string';

        return {
          component: Wrapper,
          __vars__,
          __expr__,
          isHTMLNode: isHTMLNode,
          hasHook: node.hasHook,
          refName: node.refName,
          updateProps: (newProps) => {
            // init new doc state object
            const newState = {};
            // iterate over passed in updates
            Object.keys(newProps).forEach(k => {
              // if a tracked var was updated get its new value
              if (__vars__[k]) {
                newState[__vars__[k]] = newProps[k];
              }
            });
            this.updateState(newState);
          },
          children: [
            filterIdyllProps(node, isHTMLNode)
          ],
        };
      }
    );

    this.kids = rjs.parseSchema(transformedSchema);
  }

  scrollListener() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));
  }

  initScrollListener(el) {
    if (!el) return;

    let scroller = scrollparent(el);
    let scrollContainer;
    if (scroller === document.documentElement || scroller === document.body || scroller === window) {
      scroller = window;
      scrollContainer = scrollMonitor;
    } else {
      scrollContainer = scrollMonitor.createContainer(scroller);
    }
    Object.keys(refCache).forEach((key) => {
      const { props, domNode } = refCache[key];
      const watcher = scrollContainer.create(domNode, scrollOffsets[key]);
      hooks.forEach((hook) => {
        if (props[hook]) {
          watcher[scrollMonitorEvents[hook]](() => {
            evalExpression(this.state, props[hook], hook, evalContext)();
          });
        }
      })
      scrollWatchers.push(watcher);
    });
    scroller.addEventListener('scroll', this.scrollListener);
  }

  updateDerivedVars(newState) {
    Object.keys(this.derivedVars).forEach(dv => {
      this.derivedVars[dv].value = this.derivedVars[dv].update(
        newState,
        this.state,
      );
    });
  }

  getDerivedVars() {
    let dvs = {};
    Object.keys(this.derivedVars).forEach(dv => {
      dvs[dv] = this.derivedVars[dv].value;
    });
    return dvs;
  }

  componentDidMount() {
    const refs = getRefs();
    updateRefsCallbacks.forEach(f => f({ ...this.state, refs }));

    if (typeof this.props.context === 'function') {
      this.props.context({
        update: this.updateState.bind(this),
        data: () => {
          return this.state
        },
        onUpdate: (cb) => {
          this._onUpdateState = cb;
        }
      });
    }
  }

  render() {
    return (

        {this.kids}

    )
  }
}

IdyllRuntime.defaultProps = {
  layout: 'blog',
  theme: 'github',
  insertStyles: false
};

export default IdyllRuntime;
<|endoftext|>


<commit_before>'use strict';

var ZOOM = 1;
var OFFSET_PX = 1;
var MIN_WIDTH = 12;
var MIN_HEIGHT = 8;

function animationDuration(distance) {
	return Math.pow(inRange(distance, 32, 1024), 1/3) * 50;
}

function easing(x) {
	return 1 - (1 - Math.pow(x, 1/4));
}

var keyDownTime = 0;

document.documentElement.addEventListener('keydown', function(event) {
	var code = event.which;
	// Show animation only upon Tab or Arrow keys press.
	if (code === 9 || (code > 36 && code < 41)) {
		keyDownTime = Date.now();
	}
}, false);

var prevFocused = null;
var isFirstFocus = true;
var prev = null;
var current = null;
var animationId = 0;

document.documentElement.addEventListener('focus', function(event) {
	var target = event.target;
	if (target.id === 'focus-snail') {
		return;
	}
	var offset = offsetOf(target);
	prev = current;
	current = {
		left: offset.left - OFFSET_PX,
		top: offset.top - OFFSET_PX,
		width: Math.max(MIN_WIDTH, target.offsetWidth) + 2*OFFSET_PX,
		height: Math.max(MIN_HEIGHT, target.offsetHeight) + 2*OFFSET_PX
	};

	if (isFirstFocus) {
		isFirstFocus = false;
		return;
	}

	if (Date.now() - keyDownTime > 42) {
		return;
	}

	if (!svg) {
		initialize();
	}

	onEnd();

	prevFocused = target;

	var left = 0;
	var prevLeft = 0;
	var top = 0;
	var prevTop = 0;
	var isFirstCall = true;

	var distance = euclideanDistance(prev, current);
	var duration = animationDuration(distance);

	animate(function(step) {
		if (isFirstCall) {
			setup();
			isFirstCall = false;
		}

		var e = easing(step);
		tick(
			between(prevLeft + (prev.width - ZOOM * prev.width)  / 2, left, e),
			between(prevTop + (prev.height - ZOOM * prev.height) / 2, top, e),
			between(ZOOM * prev.width,  current.width,  e),
			between(ZOOM * prev.height, current.height, e)
		);

		if (step >= 1) {
			onEnd();
		}
	}, duration);

	var isFirstTick = true;
	function tick(_left, _top, _width, _height) {
		if (isFirstTick) {
			isFirstTick = false;
			updateGradient(_left, _top, _width, _height, left, top, current.width, current.height);
		}
		var list = getPointsList({
			top: _top,
			right: _left + _width,
			bottom: _top + _height,
			left: _left
		}, {
			top: top,
			right: left + current.width,
			bottom: top + current.height,
			left: left
		});
		enclose(list, polygon);
	}

	function setup() {
		var scroll = scrollOffset();
		svg.style.left = scroll.left + 'px';
		svg.style.top = scroll.top + 'px';
		svg.setAttribute('width', window.innerWidth);
		svg.setAttribute('height', window.innerHeight);
		svg.classList.add('focus-snail_visible');
		left = current.left - scroll.left;
		prevLeft = prev.left - scroll.left;
		top = current.top - scroll.top;
		prevTop = prev.top - scroll.top;
	}

}, true);

function updateGradient(ax, ay, aWidth, aHeight, bx, by, bWidth, bHeight) {
	var midA = {
		x: ax + aWidth / 2,
		y: ay + aHeight / 2
	};
	var midB = {
		x: bx + bWidth / 2,
		y: by + bHeight / 2
	};

	var minX = Math.min(midA.x, midB.x);
	var minY = Math.min(midA.y, midB.y);

	midA.x -= minX;
	midA.y -= minY;
	midB.x -= minX;
	midB.y -= minY;

	if (maxAt(midA, midB)) {
		return;
	}
	gradient.setAttribute('x1', midA.x);
	gradient.setAttribute('y1', midA.y);
	gradient.setAttribute('x2', midB.x);
	gradient.setAttribute('y2', midB.y);
}

function maxAt(ap, bp) {
	var max = Math.max(ap.x, ap.y, bp.x, bp.y);
	if (max == 0) {
		return true;
	}
	ap.x /= max;
	ap.y /= max;
	bp.x /= max;
	bp.y /= max;
	return false;
}

var svg = null;
var polygon = null;
var start = null;
var end = null;
var gradient = null;

function initialize() {
	var dict = htmlFragment('<svg id="focus-snail_svg" width="1000" height="800" xmlns:xlink="http://www.w3.org/1999/xlink">\
		<linearGradient id="focus-snail_gradient">\
			<stop id="focus-snail_start" offset="0%" stop-color="rgb(91, 157, 217)" stop-opacity="0.3"/>\
			<stop id="focus-snail_end" offset="100%" stop-color="rgb(91, 157, 217)" stop-opacity="0.7"/>\
		</linearGradient>\
		<polygon id="focus-snail_polygon" fill="url(#focus-snail_gradient)"/>\
	</svg>', 'focus-snail_');
	svg = dict.svg;
	polygon = dict.polygon;
	start = dict.start;
	end = dict.end;
	gradient = dict.gradient;
	document.body.appendChild(svg);
}

function onEnd() {
	if (animationId) {
		cancelAnimationFrame(animationId);
		animationId = 0;
	}
	svg.classList.remove('focus-snail_visible');
	prevFocused = null;
}

function euclideanDistance(a ,b) {
	var dx = a.left - b.left;
	var dy = a.top - b.top;
	return Math.sqrt(dx*dx + dy*dy);
}

function inRange(value, from, to) {
	if (value <= from) {
		return from;
	}
	if (value >= to) {
		return to;
	}
	return value;
}

function between(from, to, value) {
	if (value <= 0) {
		return from;
	}
	if (value >= 1) {
		return to;
	}
	return from * (1 - value) + to * value;
}

function animate(onStep, duration) {
	var start = Date.now();
	(function loop() {
		animationId = requestAnimationFrame(function() {
			var diff = Date.now() - start;
			var x = diff / duration;
			onStep(x);
			if (diff < duration) {
				loop();
			}
		});
	})();
}

function getPointsList(a, b) {
	var x = 0;

	if (a.top < b.top)
		x = 1;

	if (a.right > b.right)
		x += 2;

	if (a.bottom > b.bottom)
		x += 4;

	if (a.left < b.left)
		x += 8;

	var dict = [
		[],
		[0, 1],
		[1, 2],
		[0, 1, 2],
		[2, 3],
		[0, 1], // FIXME: do two polygons
		[1, 2, 3],
		[0, 1, 2, 3],
		[3, 0],
		[3, 0, 1],
		[3, 0], // FIXME: do two polygons
		[3, 0, 1, 2],
		[2, 3, 0],
		[2, 3, 0, 1],
		[1, 2, 3, 0],
		[0, 1, 2, 3, 0]
	];

	var points = rectPoints(a).concat(rectPoints(b));
	var list = [];
	var indexes = dict[x];
	for (var i = 0; i < indexes.length; i++) {
		list.push(points[indexes[i]]);
	}
	while (i--) {
		list.push(points[indexes[i] + 4]);
	}
	return list;
}

function enclose(list, polygon) {
	polygon.points.clear();
	for (var i = 0; i < list.length; i++) {
		var p = list[i];
		addPoint(polygon, p);
	}
}

function addPoint(polygon, point) {
	var pt = svg.createSVGPoint();
	pt.x = point.x;
	pt.y = point.y;
	polygon.points.appendItem(pt);
}

function rectPoints(rect) {
	return [
		{
			x: rect.left,
			y: rect.top
		},
		{
			x: rect.right,
			y: rect.top
		},
		{
			x: rect.right,
			y: rect.bottom
		},
		{
			x: rect.left,
			y: rect.bottom
		}
	]
}

function htmlFragment(content, prefix) {
	var dummy = document.createElement('div');
	dummy.innerHTML = content;
	var all = dummy.querySelectorAll('*');
	var result = {};
	for (var i = 0; i < all.length; i++) {
		var element = all[i];
		var id = element.id;
		if (prefix) {
			id = id.slice(prefix.length);
		}
		result[id] = element;
	}
	return result;
}

function scrollOffset() {
	var win = document.defaultView;
	var docElem = document.documentElement;
	var body = document.body;
	var top = win.pageYOffset || docElem.scrollTop  || body.scrollTop;
	var left = win.pageXOffset || docElem.scrollLeft || body.scrollLeft;
	return {
		top: top,
		left: left
	};
}

function offsetOf(elem) {
	var rect = elem.getBoundingClientRect();
	var docElem = document.documentElement;
	var body = document.body;

	var scroll = scrollOffset();

	var clientTop  = docElem.clientTop  || body.clientTop  || 0,
			clientLeft = docElem.clientLeft || body.clientLeft || 0,
			top  = rect.top  + scroll.top  - clientTop,
			left = rect.left + scroll.left - clientLeft;

	return {top: top, left: left};
}
<commit_msg>When tabbing during animation, stop the current one.<commit_after>'use strict';

var ZOOM = 1;
var OFFSET_PX = 1;
var MIN_WIDTH = 12;
var MIN_HEIGHT = 8;

function animationDuration(distance) {
	return Math.pow(inRange(distance, 32, 1024), 1/3) * 50;
}

function easing(x) {
	return 1 - (1 - Math.pow(x, 1/4));
}

var keyDownTime = 0;

document.documentElement.addEventListener('keydown', function(event) {
	var code = event.which;
	// Show animation only upon Tab or Arrow keys press.
	if (code === 9 || (code > 36 && code < 41)) {
		keyDownTime = Date.now();
	}
}, false);

var prevFocused = null;
var isFirstFocus = true;
var prev = null;
var current = null;
var animationId = 0;

document.documentElement.addEventListener('focus', function(event) {
	var target = event.target;
	if (target.id === 'focus-snail') {
		return;
	}
	var offset = offsetOf(target);
	prev = current;
	current = {
		left: offset.left - OFFSET_PX,
		top: offset.top - OFFSET_PX,
		width: Math.max(MIN_WIDTH, target.offsetWidth) + 2*OFFSET_PX,
		height: Math.max(MIN_HEIGHT, target.offsetHeight) + 2*OFFSET_PX
	};

	if (isFirstFocus) {
		isFirstFocus = false;
		return;
	}

	if (Date.now() - keyDownTime > 42) {
		return;
	}

	if (!svg) {
		initialize();
	}

	onEnd();

	prevFocused = target;

	var left = 0;
	var prevLeft = 0;
	var top = 0;
	var prevTop = 0;
	var isFirstCall = true;

	var distance = euclideanDistance(prev, current);
	var duration = animationDuration(distance);

	animate(function(step) {
		if (isFirstCall) {
			setup();
			isFirstCall = false;
		}

		var e = easing(step);
		tick(
			between(prevLeft + (prev.width - ZOOM * prev.width)  / 2, left, e),
			between(prevTop + (prev.height - ZOOM * prev.height) / 2, top, e),
			between(ZOOM * prev.width,  current.width,  e),
			between(ZOOM * prev.height, current.height, e)
		);

		if (step >= 1) {
			onEnd();
		}
	}, duration);

	var isFirstTick = true;
	function tick(_left, _top, _width, _height) {
		if (isFirstTick) {
			isFirstTick = false;
			updateGradient(_left, _top, _width, _height, left, top, current.width, current.height);
		}
		var list = getPointsList({
			top: _top,
			right: _left + _width,
			bottom: _top + _height,
			left: _left
		}, {
			top: top,
			right: left + current.width,
			bottom: top + current.height,
			left: left
		});
		enclose(list, polygon);
	}

	function setup() {
		var scroll = scrollOffset();
		svg.style.left = scroll.left + 'px';
		svg.style.top = scroll.top + 'px';
		svg.setAttribute('width', window.innerWidth);
		svg.setAttribute('height', window.innerHeight);
		svg.classList.add('focus-snail_visible');
		left = current.left - scroll.left;
		prevLeft = prev.left - scroll.left;
		top = current.top - scroll.top;
		prevTop = prev.top - scroll.top;
	}

}, true);

document.documentElement.addEventListener('blur', function() {
	onEnd();
}, true);

function updateGradient(ax, ay, aWidth, aHeight, bx, by, bWidth, bHeight) {
	var midA = {
		x: ax + aWidth / 2,
		y: ay + aHeight / 2
	};
	var midB = {
		x: bx + bWidth / 2,
		y: by + bHeight / 2
	};

	var minX = Math.min(midA.x, midB.x);
	var minY = Math.min(midA.y, midB.y);

	midA.x -= minX;
	midA.y -= minY;
	midB.x -= minX;
	midB.y -= minY;

	if (maxAt(midA, midB)) {
		return;
	}
	gradient.setAttribute('x1', midA.x);
	gradient.setAttribute('y1', midA.y);
	gradient.setAttribute('x2', midB.x);
	gradient.setAttribute('y2', midB.y);
}

function maxAt(ap, bp) {
	var max = Math.max(ap.x, ap.y, bp.x, bp.y);
	if (max == 0) {
		return true;
	}
	ap.x /= max;
	ap.y /= max;
	bp.x /= max;
	bp.y /= max;
	return false;
}

var svg = null;
var polygon = null;
var start = null;
var end = null;
var gradient = null;

function initialize() {
	var dict = htmlFragment('<svg id="focus-snail_svg" width="1000" height="800" xmlns:xlink="http://www.w3.org/1999/xlink">\
		<linearGradient id="focus-snail_gradient">\
			<stop id="focus-snail_start" offset="0%" stop-color="rgb(91, 157, 217)" stop-opacity="0.3"/>\
			<stop id="focus-snail_end" offset="100%" stop-color="rgb(91, 157, 217)" stop-opacity="0.7"/>\
		</linearGradient>\
		<polygon id="focus-snail_polygon" fill="url(#focus-snail_gradient)"/>\
	</svg>', 'focus-snail_');
	svg = dict.svg;
	polygon = dict.polygon;
	start = dict.start;
	end = dict.end;
	gradient = dict.gradient;
	document.body.appendChild(svg);
}

function onEnd() {
	if (animationId) {
		cancelAnimationFrame(animationId);
		animationId = 0;
	}
	svg.classList.remove('focus-snail_visible');
	prevFocused = null;
}

function euclideanDistance(a ,b) {
	var dx = a.left - b.left;
	var dy = a.top - b.top;
	return Math.sqrt(dx*dx + dy*dy);
}

function inRange(value, from, to) {
	if (value <= from) {
		return from;
	}
	if (value >= to) {
		return to;
	}
	return value;
}

function between(from, to, value) {
	if (value <= 0) {
		return from;
	}
	if (value >= 1) {
		return to;
	}
	return from * (1 - value) + to * value;
}

function animate(onStep, duration) {
	var start = Date.now();
	(function loop() {
		animationId = requestAnimationFrame(function() {
			var diff = Date.now() - start;
			var x = diff / duration;
			onStep(x);
			if (diff < duration) {
				loop();
			}
		});
	})();
}

function getPointsList(a, b) {
	var x = 0;

	if (a.top < b.top)
		x = 1;

	if (a.right > b.right)
		x += 2;

	if (a.bottom > b.bottom)
		x += 4;

	if (a.left < b.left)
		x += 8;

	var dict = [
		[],
		[0, 1],
		[1, 2],
		[0, 1, 2],
		[2, 3],
		[0, 1], // FIXME: do two polygons
		[1, 2, 3],
		[0, 1, 2, 3],
		[3, 0],
		[3, 0, 1],
		[3, 0], // FIXME: do two polygons
		[3, 0, 1, 2],
		[2, 3, 0],
		[2, 3, 0, 1],
		[1, 2, 3, 0],
		[0, 1, 2, 3, 0]
	];

	var points = rectPoints(a).concat(rectPoints(b));
	var list = [];
	var indexes = dict[x];
	for (var i = 0; i < indexes.length; i++) {
		list.push(points[indexes[i]]);
	}
	while (i--) {
		list.push(points[indexes[i] + 4]);
	}
	return list;
}

function enclose(list, polygon) {
	polygon.points.clear();
	for (var i = 0; i < list.length; i++) {
		var p = list[i];
		addPoint(polygon, p);
	}
}

function addPoint(polygon, point) {
	var pt = svg.createSVGPoint();
	pt.x = point.x;
	pt.y = point.y;
	polygon.points.appendItem(pt);
}

function rectPoints(rect) {
	return [
		{
			x: rect.left,
			y: rect.top
		},
		{
			x: rect.right,
			y: rect.top
		},
		{
			x: rect.right,
			y: rect.bottom
		},
		{
			x: rect.left,
			y: rect.bottom
		}
	]
}

function htmlFragment(content, prefix) {
	var dummy = document.createElement('div');
	dummy.innerHTML = content;
	var all = dummy.querySelectorAll('*');
	var result = {};
	for (var i = 0; i < all.length; i++) {
		var element = all[i];
		var id = element.id;
		if (prefix) {
			id = id.slice(prefix.length);
		}
		result[id] = element;
	}
	return result;
}

function scrollOffset() {
	var win = document.defaultView;
	var docElem = document.documentElement;
	var body = document.body;
	var top = win.pageYOffset || docElem.scrollTop  || body.scrollTop;
	var left = win.pageXOffset || docElem.scrollLeft || body.scrollLeft;
	return {
		top: top,
		left: left
	};
}

function offsetOf(elem) {
	var rect = elem.getBoundingClientRect();
	var docElem = document.documentElement;
	var body = document.body;

	var scroll = scrollOffset();

	var clientTop  = docElem.clientTop  || body.clientTop  || 0,
			clientLeft = docElem.clientLeft || body.clientLeft || 0,
			top  = rect.top  + scroll.top  - clientTop,
			left = rect.left + scroll.left - clientLeft;

	return {top: top, left: left};
}
<|endoftext|>


<commit_before>window.addEventListener("load", function() {
imagepopup.newgallery('imagegallery');
});
<commit_msg>Delete using_test.js<commit_after><|endoftext|>


<commit_before>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
'use strict';

import type {Config, Path} from 'types/Config';
import type {Preprocessor} from 'types/Preprocessor';

const createDirectory = require('jest-util').createDirectory;
const crypto = require('crypto');
const fileExists = require('jest-file-exists');
const fs = require('graceful-fs');
const getCacheFilePath = require('jest-haste-map').getCacheFilePath;
const path = require('path');
const stableStringify = require('json-stable-stringify');
const vm = require('vm');

const VERSION = require('../package.json').version;

type Options = {
  isInternalModule: boolean,
};

const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';

const cache: Map<string, vm.Script> = new Map();
const configToJsonMap = new Map();
// Cache regular expressions to test whether the file needs to be preprocessed
const ignoreCache: WeakMap<Config, ?RegExp> = new WeakMap();

const removeFile = (path: Path) => {
  try {
    fs.unlinkSync(path);
  } catch (e) {}
};

const getCacheKey = (
  fileData: string,
  filePath: Path,
  config: Config,
  instrument: boolean,
): string => {
  if (!configToJsonMap.has(config)) {
    // We only need this set of config options that can likely influence
    // cached output instead of all config options.
    configToJsonMap.set(config, stableStringify({
      cacheDirectory: config.cacheDirectory,
      collectCoverage: config.collectCoverage,
      collectCoverageOnlyFrom: config.collectCoverageOnlyFrom,
      coveragePathIgnorePatterns: config.coveragePathIgnorePatterns,
      haste: config.haste,
      mocksPattern: config.mocksPattern,
      moduleFileExtensions: config.moduleFileExtensions,
      moduleNameMapper: config.moduleNameMapper,
      rootDir: config.rootDir,
      testPathDirs: config.testPathDirs,
      testRegex: config.testRegex,
    }));
  }
  const confStr = configToJsonMap.get(config) || '';
  const preprocessor = getPreprocessor(config);

  if (preprocessor && typeof preprocessor.getCacheKey === 'function') {
    return preprocessor.getCacheKey(fileData, filePath, confStr, {instrument});
  } else {
    return crypto.createHash('md5')
      .update(fileData)
      .update(confStr)
      .digest('hex');
  }
};

const writeCacheFile = (cachePath: Path, fileData: string) => {
  try {
    fs.writeFileSync(cachePath, fileData, 'utf8');
  } catch (e) {
    e.message = 'jest: failed to cache transform results in: ' + cachePath;
    removeFile(cachePath);
    throw e;
  }
};

const wrap = content => '({"' +
  EVAL_RESULT_VARIABLE +
  '":function(module,exports,require,__dirname,__filename,global,jest){' +
   content +
   '\n}});';

const readCacheFile = (filePath: Path, cachePath: Path): ?string => {
  if (!fileExists(cachePath)) {
    return null;
  }

  let fileData;
  try {
    fileData = fs.readFileSync(cachePath, 'utf8');
  } catch (e) {
    e.message = 'jest: failed to read cache file: ' + cachePath;
    removeFile(cachePath);
    throw e;
  }

  if (fileData == null) {
    // We must have somehow created the file but failed to write to it,
    // let's delete it and retry.
    removeFile(cachePath);
  }
  return fileData;
};

const getScriptCacheKey = (filename, config, instrument: boolean) => {
  const mtime = fs.statSync(filename).mtime;
  return filename + '_' + mtime.getTime() +
    (instrument ? '_instrumented' : '');
};

const shouldPreprocess = (filename: Path, config: Config): boolean => {
  if (!ignoreCache.has(config)) {
    if (!config.preprocessorIgnorePatterns) {
      ignoreCache.set(config, null);
    } else {
      ignoreCache.set(
        config,
        new RegExp(config.preprocessorIgnorePatterns.join('|')),
      );
    }
  }

  const ignoreRegexp = ignoreCache.get(config);
  const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
  return config.scriptPreprocessor && (
    !config.preprocessorIgnorePatterns.length ||
    !isIgnored
  );
};

const shouldInstrument = (filename: Path, config: Config): boolean => {
  if (!config.collectCoverage) {
    return false;
  }

  if (config.testRegex && filename.match(config.testRegex)) {
    return false;
  }

  if (
    // This configuration field contains an object in the form of:
    // {'path/to/file.js': true}
    config.collectCoverageOnlyFrom &&
    !config.collectCoverageOnlyFrom[filename]
  ) {
    return false;
  }

  if (
    config.coveragePathIgnorePatterns &&
    config.coveragePathIgnorePatterns.some(pattern => filename.match(pattern))
  ) {
    return false;
  }

  if (config.mocksPattern && filename.match(config.mocksPattern)) {
    return false;
  }

  return true;
};

const getFileCachePath = (
  filename: Path,
  config: Config,
  content: string,
  instrument: boolean,
): Path => {
  const baseCacheDir = getCacheFilePath(
    config.cacheDirectory,
    'jest-transform-cache-' + config.name,
    VERSION,
  );
  const cacheKey = getCacheKey(content, filename, config, instrument);
  // Create sub folders based on the cacheKey to avoid creating one
  // directory with many files.
  const cacheDir = path.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
  const cachePath = path.join(
    cacheDir,
    path.basename(filename, path.extname(filename)) + '_' + cacheKey,
  );
  createDirectory(cacheDir);

  return cachePath;
};

const preprocessorCache = new WeakMap();

const getPreprocessor = (config: Config): ?Preprocessor => {
  if (preprocessorCache.has(config)) {
    return preprocessorCache.get(config);
  } else {
    let preprocessor;
    if (!config.scriptPreprocessor) {
      preprocessor = null;
    } else {
      // $FlowFixMe
      preprocessor = require(config.scriptPreprocessor);
      if (typeof preprocessor.process !== 'function') {
        throw new TypeError(
          'Jest: a preprocessor must export a `process` function.',
        );
      }
    }
    preprocessorCache.set(config, preprocessor);
    return preprocessor;
  }
};

const stripShebang = content => {
  // If the file data starts with a shebang remove it. Leaves the empty line
  // to keep stack trace line numbers correct.
  if (content.startsWith('#!')) {
    return content.replace(/^#!.*/, '');
  } else {
    return content;
  }
};

const instrumentFile = (content: string, filename: Path): string => {
  // NOTE: Keeping these requires inside this function reduces a single run
  // time by 2sec if not running in `--coverage` mode
  const babel = require('babel-core');
  const babelPluginIstanbul = require('babel-plugin-istanbul').default;

  if (babel.util.canCompile(filename)) {
    return babel.transform(content, {
      filename,
      auxiliaryCommentBefore: ' istanbul ignore next ',
      plugins: [
        [
          babelPluginIstanbul,
          // right now babel-plugin-istanbul doesn't have any configuration
          // for bypassing the excludes check, but there is a config for
          // overwriting it. `.^` as a regexp that matches nothing.
          // @see https://github.com/istanbuljs/test-exclude/issues/7
          {exclude: ['.^']},
        ],
      ],
      retainLines: true,
      babelrc: false,
    }).code;
  }
  return content;
};

const cachedTransformAndWrap = (
  filename: Path,
  config: Config,
  content: string,
  instrument: boolean,
): string => {
  const preprocessor = getPreprocessor(config);
  const cacheFilePath = getFileCachePath(filename, config, content, instrument);
  // Ignore cache if `config.cache` is set (--no-cache)
  let result = config.cache ? readCacheFile(filename, cacheFilePath) : null;

  if (result) {
    return result;
  }

  result = content;

  if (preprocessor && shouldPreprocess(filename, config)) {
    result = preprocessor.process(result, filename, config, {instrument});
  }

  // That means that the preprocessor has a custom instrumentation
  // logic and will handle it based on `config.collectCoverage` option
  const preprocessorWillInstrument = preprocessor && preprocessor.INSTRUMENTS;

  if (!preprocessorWillInstrument && instrument) {
    result = instrumentFile(result, filename, config);
  }

  result = wrap(result);
  writeCacheFile(cacheFilePath, result);
  return result;
};

const transformAndBuildScript = (
  filename: Path,
  config: Config,
  options?: Options,
  instrument: boolean,
): vm.Script => {
  const isInternalModule = !!(options && options.isInternalModule);
  const content = stripShebang(fs.readFileSync(filename, 'utf8'));
  let wrappedResult;

  if (
    !isInternalModule &&
      (shouldPreprocess(filename, config) || instrument)
  ) {
    wrappedResult =
      cachedTransformAndWrap(filename, config, content, instrument);
  } else {
    wrappedResult = wrap(content);
  }

  return new vm.Script(wrappedResult, {displayErrors: true, filename});
};

module.exports = (
  filename: Path,
  config: Config,
  options?: Options,
): vm.Script => {
  const instrument = shouldInstrument(filename, config);
  const scriptCacheKey = getScriptCacheKey(filename, config, instrument);
  let script = cache.get(scriptCacheKey);
  if (script) {
    return script;
  } else {
    script = transformAndBuildScript(filename, config, options, instrument);
    cache.set(scriptCacheKey, script);
    return script;
  }
};

module.exports.EVAL_RESULT_VARIABLE = EVAL_RESULT_VARIABLE;
<commit_msg>Fix transform options.<commit_after>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
'use strict';

import type {Config, Path} from 'types/Config';
import type {Preprocessor} from 'types/Preprocessor';

const createDirectory = require('jest-util').createDirectory;
const crypto = require('crypto');
const fileExists = require('jest-file-exists');
const fs = require('graceful-fs');
const getCacheFilePath = require('jest-haste-map').getCacheFilePath;
const path = require('path');
const stableStringify = require('json-stable-stringify');
const vm = require('vm');

const VERSION = require('../package.json').version;

type Options = {
  isInternalModule: boolean,
};

const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';

const cache: Map<string, vm.Script> = new Map();
const configToJsonMap = new Map();
// Cache regular expressions to test whether the file needs to be preprocessed
const ignoreCache: WeakMap<Config, ?RegExp> = new WeakMap();

const removeFile = (path: Path) => {
  try {
    fs.unlinkSync(path);
  } catch (e) {}
};

const getCacheKey = (
  fileData: string,
  filePath: Path,
  config: Config,
  instrument: boolean,
): string => {
  if (!configToJsonMap.has(config)) {
    // We only need this set of config options that can likely influence
    // cached output instead of all config options.
    configToJsonMap.set(config, stableStringify({
      cacheDirectory: config.cacheDirectory,
      collectCoverage: config.collectCoverage,
      collectCoverageOnlyFrom: config.collectCoverageOnlyFrom,
      coveragePathIgnorePatterns: config.coveragePathIgnorePatterns,
      haste: config.haste,
      mocksPattern: config.mocksPattern,
      moduleFileExtensions: config.moduleFileExtensions,
      moduleNameMapper: config.moduleNameMapper,
      rootDir: config.rootDir,
      testPathDirs: config.testPathDirs,
      testRegex: config.testRegex,
    }));
  }
  const confStr = configToJsonMap.get(config) || '';
  const preprocessor = getPreprocessor(config);

  if (preprocessor && typeof preprocessor.getCacheKey === 'function') {
    return preprocessor.getCacheKey(fileData, filePath, confStr, {instrument});
  } else {
    return crypto.createHash('md5')
      .update(fileData)
      .update(confStr)
      .digest('hex');
  }
};

const writeCacheFile = (cachePath: Path, fileData: string) => {
  try {
    fs.writeFileSync(cachePath, fileData, 'utf8');
  } catch (e) {
    e.message = 'jest: failed to cache transform results in: ' + cachePath;
    removeFile(cachePath);
    throw e;
  }
};

const wrap = content => '({"' +
  EVAL_RESULT_VARIABLE +
  '":function(module,exports,require,__dirname,__filename,global,jest){' +
   content +
   '\n}});';

const readCacheFile = (filePath: Path, cachePath: Path): ?string => {
  if (!fileExists(cachePath)) {
    return null;
  }

  let fileData;
  try {
    fileData = fs.readFileSync(cachePath, 'utf8');
  } catch (e) {
    e.message = 'jest: failed to read cache file: ' + cachePath;
    removeFile(cachePath);
    throw e;
  }

  if (fileData == null) {
    // We must have somehow created the file but failed to write to it,
    // let's delete it and retry.
    removeFile(cachePath);
  }
  return fileData;
};

const getScriptCacheKey = (filename, config, instrument: boolean) => {
  const mtime = fs.statSync(filename).mtime;
  return filename + '_' + mtime.getTime() +
    (instrument ? '_instrumented' : '');
};

const shouldPreprocess = (filename: Path, config: Config): boolean => {
  if (!ignoreCache.has(config)) {
    if (!config.preprocessorIgnorePatterns) {
      ignoreCache.set(config, null);
    } else {
      ignoreCache.set(
        config,
        new RegExp(config.preprocessorIgnorePatterns.join('|')),
      );
    }
  }

  const ignoreRegexp = ignoreCache.get(config);
  const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
  return config.scriptPreprocessor && (
    !config.preprocessorIgnorePatterns.length ||
    !isIgnored
  );
};

const shouldInstrument = (filename: Path, config: Config): boolean => {
  if (!config.collectCoverage) {
    return false;
  }

  if (config.testRegex && filename.match(config.testRegex)) {
    return false;
  }

  if (
    // This configuration field contains an object in the form of:
    // {'path/to/file.js': true}
    config.collectCoverageOnlyFrom &&
    !config.collectCoverageOnlyFrom[filename]
  ) {
    return false;
  }

  if (
    config.coveragePathIgnorePatterns &&
    config.coveragePathIgnorePatterns.some(pattern => filename.match(pattern))
  ) {
    return false;
  }

  if (config.mocksPattern && filename.match(config.mocksPattern)) {
    return false;
  }

  return true;
};

const getFileCachePath = (
  filename: Path,
  config: Config,
  content: string,
  instrument: boolean,
): Path => {
  const baseCacheDir = getCacheFilePath(
    config.cacheDirectory,
    'jest-transform-cache-' + config.name,
    VERSION,
  );
  const cacheKey = getCacheKey(content, filename, config, instrument);
  // Create sub folders based on the cacheKey to avoid creating one
  // directory with many files.
  const cacheDir = path.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
  const cachePath = path.join(
    cacheDir,
    path.basename(filename, path.extname(filename)) + '_' + cacheKey,
  );
  createDirectory(cacheDir);

  return cachePath;
};

const preprocessorCache = new WeakMap();

const getPreprocessor = (config: Config): ?Preprocessor => {
  if (preprocessorCache.has(config)) {
    return preprocessorCache.get(config);
  } else {
    let preprocessor;
    if (!config.scriptPreprocessor) {
      preprocessor = null;
    } else {
      // $FlowFixMe
      preprocessor = require(config.scriptPreprocessor);
      if (typeof preprocessor.process !== 'function') {
        throw new TypeError(
          'Jest: a preprocessor must export a `process` function.',
        );
      }
    }
    preprocessorCache.set(config, preprocessor);
    return preprocessor;
  }
};

const stripShebang = content => {
  // If the file data starts with a shebang remove it. Leaves the empty line
  // to keep stack trace line numbers correct.
  if (content.startsWith('#!')) {
    return content.replace(/^#!.*/, '');
  } else {
    return content;
  }
};

const instrumentFile = (content: string, filename: Path): string => {
  // NOTE: Keeping these requires inside this function reduces a single run
  // time by 2sec if not running in `--coverage` mode
  const babel = require('babel-core');
  const babelPluginIstanbul = require('babel-plugin-istanbul').default;

  if (babel.util.canCompile(filename)) {
    return babel.transform(content, {
      filename,
      auxiliaryCommentBefore: ' istanbul ignore next ',
      plugins: [
        [
          babelPluginIstanbul,
          // right now babel-plugin-istanbul doesn't have any configuration
          // for bypassing the excludes check, but there is a config for
          // overwriting it. `.^` as a regexp that matches nothing.
          // @see https://github.com/istanbuljs/test-exclude/issues/7
          {exclude: ['.^']},
        ],
      ],
      retainLines: true,
      babelrc: false,
    }).code;
  }
  return content;
};

const cachedTransformAndWrap = (
  filename: Path,
  config: Config,
  content: string,
  instrument: boolean,
): string => {
  const preprocessor = getPreprocessor(config);
  const cacheFilePath = getFileCachePath(filename, config, content, instrument);
  // Ignore cache if `config.cache` is set (--no-cache)
  let result = config.cache ? readCacheFile(filename, cacheFilePath) : null;

  if (result) {
    return result;
  }

  result = content;

  if (preprocessor && shouldPreprocess(filename, config)) {
    result = preprocessor.process(result, filename, config, {instrument});
  }

  // That means that the preprocessor has a custom instrumentation
  // logic and will handle it based on `config.collectCoverage` option
  const preprocessorWillInstrument = preprocessor && preprocessor.INSTRUMENTS;

  if (!preprocessorWillInstrument && instrument) {
    result = instrumentFile(result, filename, config);
  }

  result = wrap(result);
  writeCacheFile(cacheFilePath, result);
  return result;
};

const transformAndBuildScript = (
  filename: Path,
  config: Config,
  options: ?Options,
  instrument: boolean,
): vm.Script => {
  const isInternalModule = !!(options && options.isInternalModule);
  const content = stripShebang(fs.readFileSync(filename, 'utf8'));
  let wrappedResult;

  if (
    !isInternalModule &&
      (shouldPreprocess(filename, config) || instrument)
  ) {
    wrappedResult =
      cachedTransformAndWrap(filename, config, content, instrument);
  } else {
    wrappedResult = wrap(content);
  }

  return new vm.Script(wrappedResult, {displayErrors: true, filename});
};

module.exports = (
  filename: Path,
  config: Config,
  options: ?Options,
): vm.Script => {
  const instrument = shouldInstrument(filename, config);
  const scriptCacheKey = getScriptCacheKey(filename, config, instrument);
  let script = cache.get(scriptCacheKey);
  if (script) {
    return script;
  } else {
    script = transformAndBuildScript(filename, config, options, instrument);
    cache.set(scriptCacheKey, script);
    return script;
  }
};

module.exports.EVAL_RESULT_VARIABLE = EVAL_RESULT_VARIABLE;
<|endoftext|>


<commit_before>'use strict';

var BRp = {};

BRp.getCachedImage = function( url, onLoad ){
  var r = this;
  var imageCache = r.imageCache = r.imageCache || {};

  if( imageCache[ url ] && imageCache[ url ].image ){
    return imageCache[ url ].image;
  }

  var cache = imageCache[ url ] = imageCache[ url ] || {};

  var image = cache.image = new Image();
  image.addEventListener( 'load', onLoad );
  image.src = url;

  return image;
};

module.exports = BRp;
<commit_msg>Prevent tainted canvas<commit_after>'use strict';

var BRp = {};

BRp.getCachedImage = function( url, onLoad ){
  var r = this;
  var imageCache = r.imageCache = r.imageCache || {};

  if( imageCache[ url ] && imageCache[ url ].image ){
    return imageCache[ url ].image;
  }

  var cache = imageCache[ url ] = imageCache[ url ] || {};

  var image = cache.image = new Image();
  image.addEventListener('load', onLoad);
  image.crossOrigin = 'Anonymous'; // prevent tainted canvas
  image.src = url;

  return image;
};

module.exports = BRp;
<|endoftext|>


<commit_before>// Extensions

// keep this wrapper around the ones you use!
(function( undefined ){

	var xmlHttp = (function() {
		var xmlhttpmethod = false;
		try {
			xmlhttpmethod = new XMLHttpRequest();
		}
		catch( e ){
			xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" );
		}
		return function(){
			return xmlhttpmethod;
		};
	}());

	shoestring.ajax = function( url, options ) {
		var req = xmlHttp(),
		settings = shoestring.ajax.settings;

		if( options ){
			shoestring.extend( settings, options );
		}
		if( !url ){
			url = settings.url;
		}

		if( !req || !url ){
			return;
		}

		req.open( settings.method, url, settings.async );

		if( req.setRequestHeader ){
			req.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
		}

		req.onreadystatechange = function () {
			if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){
				return settings.error( req.responseText, req.status, req );
			}
			settings.success( req.responseText, req.status, req );
		};
		if( req.readyState === 4 ){
			return;
		}

		req.send( null );
	};

	shoestring.ajax.settings = {
		success: function(){},
		error: function(){},
		method: "GET",
		async: true,
		data: null
	};
}());<commit_msg>Trim white-space in ajax requests to make sure they work properly if passed into shoestring as html: shoestring('<div>')<commit_after>// Extensions

// keep this wrapper around the ones you use!
(function( undefined ){

	var xmlHttp = (function() {
		var xmlhttpmethod = false;
		try {
			xmlhttpmethod = new XMLHttpRequest();
		}
		catch( e ){
			xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" );
		}
		return function(){
			return xmlhttpmethod;
		};
	}());

	shoestring.ajax = function( url, options ) {
		var req = xmlHttp(),
		settings = shoestring.ajax.settings;

		if( options ){
			shoestring.extend( settings, options );
		}
		if( !url ){
			url = settings.url;
		}

		if( !req || !url ){
			return;
		}

		req.open( settings.method, url, settings.async );

		if( req.setRequestHeader ){
			req.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
		}

		req.onreadystatechange = function () {
			// Trim the whitespace so shoestring('<div>') works
			var res = (req.responseText || '').replace(/^\s+|\s+$/g, '');
			if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){
				return settings.error( res, req.status, req );
			}
			settings.success( res, req.status, req );
		};
		if( req.readyState === 4 ){
			return;
		}

		req.send( null );
	};

	shoestring.ajax.settings = {
		success: function(){},
		error: function(){},
		method: "GET",
		async: true,
		data: null
	};
}());<|endoftext|>