<commit_before>const {DefaultTheme, DefaultThemeRenderContext, JSX, UrlMapping} = require('typedoc');

function bind(fn, first) {
  return (...r) => fn(first, ...r);
}

class SassSiteRenderContext extends DefaultThemeRenderContext {
  // Link to the external documentation of external APIs. Unfortunately, there's
  // no way to use `addUnknownSymbolResolver` for names that don't come from npm
  // packages.
  attemptExternalResolution = function(symbol) {
    if (symbol.escapedName === 'URL') {
      return 'https://developer.mozilla.org/en-US/docs/Web/API/URL';
    } else if (symbol.escapedName === 'Buffer') {
      return 'https://nodejs.org/api/buffer.html';
    } else {
      return null;
    }
  };

  // We don't include Typedoc's JS, so the default means of displaying overloads
  // as multiple togglable definitions within a single member documentation
  // doesn't work. Instead, we emit each overload as a separate entry with its
  // own panel.
  oldMember = this.member;
  member = bind(function(context, props) {
    const signatures = props?.signatures;
    if (signatures && signatures.length > 1) {
      const element = JSX.createElement(JSX.Fragment, null,
        ...signatures.map(signature => {
          props.signatures = [signature];
          return context.oldMember(props);
        }));
      props.signatures = signatures;
      return element;
    }

    return context.oldMember(props);
  }, this);

  // Make categories visible in the sidebar as well as in the main page's index.
  // Hopefully this will no longer be necessary once TypeStrong/typedoc#1532 is
  // implemented.
  oldNavigation = this.navigation;
  navigation = bind(function(context, props) {
    const navigation = context.oldNavigation(props);
    const childrenByCategories = context._groupByCategory(props.model);
    if (childrenByCategories.size === 0) return navigation;

    const secondary  = navigation.children[navigation.children.length - 1];
    if (!secondary) return navigation;

    const firstLI = secondary.children[0].children[0];
    const ul = firstLI.props["class"].startsWith("current ")
        ? firstLI.children[1]
        : secondary.children[0];

    ul.children = Array.from(childrenByCategories).map(([title, children]) =>
        JSX.createElement(JSX.Fragment, null,
            JSX.createElement("li", {class: "sl-tsd-category-label"},
                JSX.createElement("span", null, title)),
            ...children.map(child =>
                JSX.createElement("li", {class: child.cssClasses},
                    JSX.createElement("a", {
                      href: context.urlTo(child), class: "tsd-kind-icon"
                    }, child.name)))));

    return navigation;
  }, this);

  // Returns a map from category titles to the set of members of those
  // categories.
  _groupByCategory = (model) => {
    const map = new Map();
    function addCategoriesToMap(categories) {
      for (const category of categories) {
        const children = map.get(category.title);
        if (children) {
          children.push(...category.children);
        } else {
          map.set(category.title, [...category.children]);
        }
      }
    }

    if (model.categories) {
      addCategoriesToMap(model.categories);
    } else if (model.groups) {
      for (const group of model.groups) {
        if (group.categories) addCategoriesToMap(group.categories);
      }
    }

    return map;
  };

  // Add compatibility indicators to the beginning of documentation blocks.
  oldComment = this.comment;
  comment = bind((context, props) => {
    const compatibilityTags = props.comment?.tags
        .filter(tag => tag.tagName === "compatibility");
    props.comment?.removeTags("compatibility");

    const parent = this.oldComment(props);
    if (!parent) return;

    parent.children.unshift(...compatibilityTags.map(compat => {
      // The first line is arguments to impl_status, anything after that is the
      // contents of the block.
      const lineBreak = compat.text.indexOf("\n");
      const firstLine =
          lineBreak === -1 ? compat.text : compat.text.substring(0, lineBreak);
      const rest =
          lineBreak === -1 ? null : compat.text.substring(lineBreak + 1).trim();

      return JSX.createElement(JSX.Raw, {
        html: `<% impl_status(${firstLine}) ${rest ? 'do' : ''} %>` +
            context.markdown(rest) +
            (rest ? '<% end %>' : '')
      });
    }));

    return parent;
  }, this);

  // Convert paragraphs that start with **Heads up!** or **Fun fact!** into
  // proper callouts.
  oldMarkdown = this.markdown;
  markdown = bind((context, text) =>
      context.oldMarkdown(text)
          .replace(
              /
Heads up!<\/strong>([^]*?)<\/p>/g,
              '<% heads_up do %>$1<% end %>')
          .replace(
              /
Fun fact!<\/strong>([^]*?)<\/p>/g,
              '<% fun_fact do %>$1<% end %>'),
      this);
}

class SassSiteTheme extends DefaultTheme {
  constructor(renderer) {
    super(renderer);

    // This is an ugly monkeypatch but it's necessary to work around
    // TypeStrong/typedoc#1731.
    //
    // Relative URLs don't work well for index pages since they can be rendered at
    // different directory levels, so we just convert all URLs to absolute to be
    // safe.
    const ContextAwareRendererComponent =
        Object.getPrototypeOf(this.markedPlugin.constructor);
    const oldGetRelativeUrl =
        ContextAwareRendererComponent.prototype.getRelativeUrl;
    ContextAwareRendererComponent.prototype.getRelativeUrl = function(url) {
      const relative = oldGetRelativeUrl.call(this, url);

      const absolute = new URL(
        relative,
        `relative:///documentation/js-api/${this.location}`
      );
      absolute.pathname = absolute.pathname
        .replace(/\.html$/, '')
        .replace(/\/index$/, '');
      return absolute.toString().replace(/^relative:\/\//, '');
    };
  }

  getRenderContext() {
    this.contextCache ??=
        new SassSiteRenderContext(this, this.application.options);
    return this.contextCache;
  }

  render(page) {
    const context = this.getRenderContext(page);

    // The default header includes a search bar that we don't want, so we just
    // render title on its own.
    const breadcrumb = page.model.parent
      ? `
${JSX.renderElement(
          context.breadcrumb(page.model)
        )}
`
      : '';
    const heading =
      (page.model.kindString === 'Project'
        ? ''
        : `${page.model.kindString || ''} `) +
      page.model.name +
      (page.model.typeParameters
        ? `<${page.model.typeParameters
            .map(item => item.name)
            .join(', ')}>`
        : '');

    // The default template renders a full HTML document, but for the Sass site
    // we want it to be a page within the normal layout so we just render the
    // contents with a header that provides the title tag. We also wrap it in a
    // div with class "typedoc" so we can isolate the typedoc-specific CSS.
    return `---
title: ${JSON.stringify(`${page.model.name} | JS API`)}
---

        ${breadcrumb}

${heading}

        ${JSX.renderElement(page.template(page))}

        ${JSX.renderElement(context.navigation(page))}

`;
  }

  getUrls(project) {
    return super
      .getUrls(project)
      .map(
        mapping =>
          new UrlMapping(`${mapping.url}.erb`, mapping.model, mapping.template)
      );
  }
}

// TODO: See if there's a graceful way to support "Heads up!" and Compatibility
// annotations as @-tags rather than needing to write out the HTML by hand.

exports.load = app => {
  app.renderer.addUnknownSymbolResolver('immutable', (name) =>
      `https://immutable-js.com/docs/latest@main/${name}/`);
  app.renderer.addUnknownSymbolResolver('source-map-js', (name) => {
    if (name === 'RawSourceMap') {
      return 'https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/source-map.d.ts#L15-L23';
    }
  });

  app.renderer.defineTheme('sass-site', SassSiteTheme);
};
<commit_msg>Make the TypeDoc theme resilient against nested child lists (#589)<commit_after>const {DefaultTheme, DefaultThemeRenderContext, JSX, UrlMapping} = require('typedoc');

function bind(fn, first) {
  return (...r) => fn(first, ...r);
}

class SassSiteRenderContext extends DefaultThemeRenderContext {
  // Link to the external documentation of external APIs. Unfortunately, there's
  // no way to use `addUnknownSymbolResolver` for names that don't come from npm
  // packages.
  attemptExternalResolution = function(symbol) {
    if (symbol.escapedName === 'URL') {
      return 'https://developer.mozilla.org/en-US/docs/Web/API/URL';
    } else if (symbol.escapedName === 'Buffer') {
      return 'https://nodejs.org/api/buffer.html';
    } else {
      return null;
    }
  };

  // We don't include Typedoc's JS, so the default means of displaying overloads
  // as multiple togglable definitions within a single member documentation
  // doesn't work. Instead, we emit each overload as a separate entry with its
  // own panel.
  oldMember = this.member;
  member = bind(function(context, props) {
    const signatures = props?.signatures;
    if (signatures && signatures.length > 1) {
      const element = JSX.createElement(JSX.Fragment, null,
        ...signatures.map(signature => {
          props.signatures = [signature];
          return context.oldMember(props);
        }));
      props.signatures = signatures;
      return element;
    }

    return context.oldMember(props);
  }, this);

  // Make categories visible in the sidebar as well as in the main page's index.
  // Hopefully this will no longer be necessary once TypeStrong/typedoc#1532 is
  // implemented.
  oldNavigation = this.navigation;
  navigation = bind(function(context, props) {
    const navigation = context.oldNavigation(props);
    const childrenByCategories = context._groupByCategory(props.model);
    if (childrenByCategories.size === 0) return navigation;

    const secondary = context._getNthChild(navigation, 1);
    if (!secondary) return navigation;

    const firstLI = context._getNthChild(context._getNthChild(secondary, 0), 0);
    const ul = firstLI.props["class"].startsWith("current ")
        ? context._getNthChild(firstLI, 1)
        : context._getNthChild(secondary, 0);

    ul.children = Array.from(childrenByCategories).map(([title, children]) =>
        JSX.createElement(JSX.Fragment, null,
            JSX.createElement("li", {class: "sl-tsd-category-label"},
                JSX.createElement("span", null, title)),
            ...children.map(child =>
                JSX.createElement("li", {class: child.cssClasses},
                    JSX.createElement("a", {
                      href: context.urlTo(child), class: "tsd-kind-icon"
                    }, child.name)))));

    return navigation;
  }, this);

  // Returns the `n`-th child of a JSX node. For some reason, JSX nodes created
  // by TypeDoc can contain nested arrays, so this traverses them.
  _getNthChild = (node, n) => {
    let i = 0;

    function traverse(array) {
      for (const element of array) {
        if (element instanceof Array) {
          const result = traverse(element);
          if (result != undefined) return result;
        } else {
          if (i === n) return element;
          i++;
        }
      }
    }

    return traverse(node.children);
  };

  // Returns a map from category titles to the set of members of those
  // categories.
  _groupByCategory = (model) => {
    const map = new Map();
    function addCategoriesToMap(categories) {
      for (const category of categories) {
        const children = map.get(category.title);
        if (children) {
          children.push(...category.children);
        } else {
          map.set(category.title, [...category.children]);
        }
      }
    }

    if (model.categories) {
      addCategoriesToMap(model.categories);
    } else if (model.groups) {
      for (const group of model.groups) {
        if (group.categories) addCategoriesToMap(group.categories);
      }
    }

    return map;
  };

  // Add compatibility indicators to the beginning of documentation blocks.
  oldComment = this.comment;
  comment = bind((context, props) => {
    const compatibilityTags = props.comment?.tags
        .filter(tag => tag.tagName === "compatibility");
    props.comment?.removeTags("compatibility");

    const parent = this.oldComment(props);
    if (!parent) return;

    parent.children.unshift(...compatibilityTags.map(compat => {
      // The first line is arguments to impl_status, anything after that is the
      // contents of the block.
      const lineBreak = compat.text.indexOf("\n");
      const firstLine =
          lineBreak === -1 ? compat.text : compat.text.substring(0, lineBreak);
      const rest =
          lineBreak === -1 ? null : compat.text.substring(lineBreak + 1).trim();

      return JSX.createElement(JSX.Raw, {
        html: `<% impl_status(${firstLine}) ${rest ? 'do' : ''} %>` +
            context.markdown(rest) +
            (rest ? '<% end %>' : '')
      });
    }));

    return parent;
  }, this);

  // Convert paragraphs that start with **Heads up!** or **Fun fact!** into
  // proper callouts.
  oldMarkdown = this.markdown;
  markdown = bind((context, text) =>
      context.oldMarkdown(text)
          .replace(
              /
Heads up!<\/strong>([^]*?)<\/p>/g,
              '<% heads_up do %>$1<% end %>')
          .replace(
              /
Fun fact!<\/strong>([^]*?)<\/p>/g,
              '<% fun_fact do %>$1<% end %>'),
      this);
}

class SassSiteTheme extends DefaultTheme {
  constructor(renderer) {
    super(renderer);

    // This is an ugly monkeypatch but it's necessary to work around
    // TypeStrong/typedoc#1731.
    //
    // Relative URLs don't work well for index pages since they can be rendered at
    // different directory levels, so we just convert all URLs to absolute to be
    // safe.
    const ContextAwareRendererComponent =
        Object.getPrototypeOf(this.markedPlugin.constructor);
    const oldGetRelativeUrl =
        ContextAwareRendererComponent.prototype.getRelativeUrl;
    ContextAwareRendererComponent.prototype.getRelativeUrl = function(url) {
      const relative = oldGetRelativeUrl.call(this, url);

      const absolute = new URL(
        relative,
        `relative:///documentation/js-api/${this.location}`
      );
      absolute.pathname = absolute.pathname
        .replace(/\.html$/, '')
        .replace(/\/index$/, '');
      return absolute.toString().replace(/^relative:\/\//, '');
    };
  }

  getRenderContext() {
    this.contextCache ??=
        new SassSiteRenderContext(this, this.application.options);
    return this.contextCache;
  }

  render(page) {
    const context = this.getRenderContext(page);

    // The default header includes a search bar that we don't want, so we just
    // render title on its own.
    const breadcrumb = page.model.parent
      ? `
${JSX.renderElement(
          context.breadcrumb(page.model)
        )}
`
      : '';
    const heading =
      (page.model.kindString === 'Project'
        ? ''
        : `${page.model.kindString || ''} `) +
      page.model.name +
      (page.model.typeParameters
        ? `<${page.model.typeParameters
            .map(item => item.name)
            .join(', ')}>`
        : '');

    // The default template renders a full HTML document, but for the Sass site
    // we want it to be a page within the normal layout so we just render the
    // contents with a header that provides the title tag. We also wrap it in a
    // div with class "typedoc" so we can isolate the typedoc-specific CSS.
    return `---
title: ${JSON.stringify(`${page.model.name} | JS API`)}
---

        ${breadcrumb}

${heading}

        ${JSX.renderElement(page.template(page))}

        ${JSX.renderElement(context.navigation(page))}

`;
  }

  getUrls(project) {
    return super
      .getUrls(project)
      .map(
        mapping =>
          new UrlMapping(`${mapping.url}.erb`, mapping.model, mapping.template)
      );
  }
}

// TODO: See if there's a graceful way to support "Heads up!" and Compatibility
// annotations as @-tags rather than needing to write out the HTML by hand.

exports.load = app => {
  app.renderer.addUnknownSymbolResolver('immutable', (name) =>
      `https://immutable-js.com/docs/latest@main/${name}/`);
  app.renderer.addUnknownSymbolResolver('source-map-js', (name) => {
    if (name === 'RawSourceMap') {
      return 'https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/source-map.d.ts#L15-L23';
    }
  });

  app.renderer.defineTheme('sass-site', SassSiteTheme);
};
<|endoftext|>


<commit_before>import { createElement } from 'react-native-web';

/**
 * The `react-native-svg` has some non-standard api's that do not match with the
 * properties that can be applied to web SVG elements. This prepare function removes
 * those properties and adds the properties back in their correct location.
 *
 * @param {Object} props Properties given to us.
 * @returns {Object} Cleaned object.
 * @private
 */
function prepare(props) {
  const {
    translate,
    scale,
    rotate,
    skewX,
    skewY,
    originX,
    originY,
    fontFamily,
    fontSize,
    fontWeight,
    fontStyle,
    style = {},
    ...clean
  } = props;

  // Correctly apply the transformation properties.
  // To apply originX and originY we need to translate the element on those values and
  // translate them back once the element is scaled, rotated and skewed.
  const transform = [];

  /* eslint-disable eqeqeq */
  if (originX != null || originY != null) {
    transform.push(`translate(${originX || 0}, ${originY || 0})`);
  }
  if (translate != null) {
    transform.push(`translate(${translate})`);
  }
  if (scale != null) {
    transform.push(`scale(${scale})`);
  }
  if (rotate != null) {
    transform.push(`rotate(${rotate})`);
  }
  if (skewX != null) {
    transform.push(`skewX(${skewX})`);
  }
  if (skewY != null) {
    transform.push(`skewY(${skewY})`);
  }
  if (originX != null || originY != null) {
    transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
  }

  if (transform.length) {
    clean.transform = transform.join(' ');
  }

  if (fontFamily != null) {
    style.fontFamily = fontFamily;
  }
  if (fontSize != null) {
    style.fontSize = fontSize;
  }
  if (fontWeight != null) {
    style.fontWeight = fontWeight;
  }
  if (fontStyle != null) {
    style.fontStyle = fontStyle;
  }
  /* eslint-enable eqeqeq */
  clean.style = style;

  // We provide a default of `xMidYMid` if aspectRatio is not specified with align information.
  const preserve = clean.preserveAspectRatio;
  if (preserve && preserve !== 'none' && !~preserve.indexOf(' ')) {
    clean.preserveAspectRatio = 'xMidYMid ' + preserve;
  }

  return clean;
}

/**
 * Return a circle SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Circle SVG.
 * @public
 */
function Circle(props) {
  return createElement('circle', prepare(props));
}

/**
 * Return a clipPath SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} ClipPath SVG.
 * @public
 */
function ClipPath(props) {
  return createElement('clipPath', prepare(props));
}

/**
 * Return a defs SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Defs SVG.
 * @public
 */
function Defs(props) {
  return createElement('defs', prepare(props));
}

/**
 * Return a ellipse SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Ellipse SVG.
 * @public
 */
function Ellipse(props) {
  return createElement('ellipse', prepare(props));
}

/**
 * Return a g SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} G SVG.
 * @public
 */
function G(props) {
  const { x, y, ...rest } = props;

  if ((x || y) && !rest.translate) {
    rest.translate = `${x || 0}, ${y || 0}`;
  }

  return createElement('g', prepare(rest));
}

/**
 * Return a image SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Image SVG.
 * @public
 */
function Image(props) {
  return createElement('image', prepare(props));
}

/**
 * Return a line SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Line SVG.
 * @public
 */
function Line(props) {
  return createElement('line', prepare(props));
}

/**
 * Return a linearGradient SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} LinearGradient SVG.
 * @public
 */
function LinearGradient(props) {
  return createElement('linearGradient', prepare(props));
}

/**
 * Return a path SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Path SVG.
 * @public
 */
function Path(props) {
  return createElement('path', prepare(props));
}

/**
 * Return a polygon SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Polygon SVG.
 * @public
 */
function Polygon(props) {
  return createElement('polygon', prepare(props));
}

/**
 * Return a polyline SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Polyline SVG.
 * @public
 */
function Polyline(props) {
  return createElement('polyline', prepare(props));
}

/**
 * Return a radialGradient SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} RadialGradient SVG.
 * @public
 */
function RadialGradient(props) {
  return createElement('radialGradient', prepare(props));
}

/**
 * Return a rect SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Rect SVG.
 * @public
 */
function Rect(props) {
  return createElement('rect', prepare(props));
}

/**
 * Return a stop SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Stop SVG.
 * @public
 */
function Stop(props) {
  return createElement('stop', prepare(props));
}

/**
 * Return a SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} SVG.
 * @public
 */
function Svg(props) {
  const { title, ...rest } = props;

  if (title) {
    return createElement(
      'svg',
      { role: 'img', 'aria-label': '[title]', ...prepare(rest) },
      [createElement('title', {}, title), props.children],
    );
  }

  return createElement('svg', prepare(rest));
}

/**
 * Return a symbol SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Symbol SVG.
 * @public
 */
function Symbol(props) {
  return createElement('symbol', prepare(props));
}

/**
 * Return a text SVG element.
 *
 * @returns {React.Component} Text SVG.
 * @public
 * @param {Object} props The properties that are spread on the SVG element.
 * @param {String} props.x x position
 * @param {String} props.y y position
 * @param {String} props.dx delta x
 * @param {String} props.dy delta y
 * @param {String} props.rotate rotation
 */
function Text(props) {
  return createElement('text', prepare(props));
}

/**
 * Return a tspan SVG element.
 *
 * @returns {React.Component} TSpan SVG.
 * @public
 * @param {Object} props The properties that are spread on the SVG element.
 * @param {String} props.x x position
 * @param {String} props.y y position
 * @param {String} props.dx delta x
 * @param {String} props.dy delta y
 * @param {String} props.rotate rotation
 */
function TSpan(props) {
  return createElement('tspan', prepare(props));
}

/**
 * Return a textpath SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} TextPath SVG.
 * @public
 */
function TextPath(props) {
  return createElement('textPath', prepare(props));
}

/**
 * Return a use SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Use(props) {
  return createElement('use', prepare(props));
}

/**
 * Return a mask SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Mask(props) {
  return createElement('mask', prepare(props));
}

/**
 * Return a pattern SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Pattern(props) {
  return createElement('pattern', prepare(props));
}

//
// Expose everything in the same way as `react-native-svg` is doing.
//
export {
  Circle,
  ClipPath,
  Defs,
  Ellipse,
  G,
  Image,
  Line,
  LinearGradient,
  Mask,
  Path,
  Pattern,
  Polygon,
  Polyline,
  RadialGradient,
  Rect,
  Stop,
  Svg,
  Symbol,
  TSpan,
  Text,
  TextPath,
  Use,
};

export default Svg;
<commit_msg>Fix handling of style arrays and react-native-web style numbers<commit_after>import { createElement, StyleSheet } from 'react-native-web';

function resolve(style1, style2) {
  if (style1 && style2) {
    return StyleSheet
      ? [style1, style2]
      : {
          // Compatibility for arrays of styles in plain react web
          ...(style1.length ? Object.assign({}, ...style1) : style1),
          ...style2,
        };
  } else {
    return style1 || style2;
  }
}

/**
 * The `react-native-svg` has some non-standard api's that do not match with the
 * properties that can be applied to web SVG elements. This prepare function removes
 * those properties and adds the properties back in their correct location.
 *
 * @param {Object} props Properties given to us.
 * @returns {Object} Cleaned object.
 * @private
 */
function prepare(props) {
  const {
    translate,
    scale,
    rotate,
    skewX,
    skewY,
    originX,
    originY,
    fontFamily,
    fontSize,
    fontWeight,
    fontStyle,
    style,
    ...clean
  } = props;

  // Correctly apply the transformation properties.
  // To apply originX and originY we need to translate the element on those values and
  // translate them back once the element is scaled, rotated and skewed.
  const transform = [];

  /* eslint-disable eqeqeq */
  if (originX != null || originY != null) {
    transform.push(`translate(${originX || 0}, ${originY || 0})`);
  }
  if (translate != null) {
    transform.push(`translate(${translate})`);
  }
  if (scale != null) {
    transform.push(`scale(${scale})`);
  }
  if (rotate != null) {
    transform.push(`rotate(${rotate})`);
  }
  if (skewX != null) {
    transform.push(`skewX(${skewX})`);
  }
  if (skewY != null) {
    transform.push(`skewY(${skewY})`);
  }
  if (originX != null || originY != null) {
    transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
  }

  if (transform.length) {
    clean.transform = transform.join(' ');
  }

  const styles = {};
  if (fontFamily != null) {
    styles.fontFamily = fontFamily;
  }
  if (fontSize != null) {
    styles.fontSize = fontSize;
  }
  if (fontWeight != null) {
    styles.fontWeight = fontWeight;
  }
  if (fontStyle != null) {
    styles.fontStyle = fontStyle;
  }
  /* eslint-enable eqeqeq */
  clean.style = resolve(
    style,
    styles,
  );

  // We provide a default of `xMidYMid` if aspectRatio is not specified with align information.
  const preserve = clean.preserveAspectRatio;
  if (preserve && preserve !== 'none' && !~preserve.indexOf(' ')) {
    clean.preserveAspectRatio = 'xMidYMid ' + preserve;
  }

  return clean;
}

/**
 * Return a circle SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Circle SVG.
 * @public
 */
function Circle(props) {
  return createElement('circle', prepare(props));
}

/**
 * Return a clipPath SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} ClipPath SVG.
 * @public
 */
function ClipPath(props) {
  return createElement('clipPath', prepare(props));
}

/**
 * Return a defs SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Defs SVG.
 * @public
 */
function Defs(props) {
  return createElement('defs', prepare(props));
}

/**
 * Return a ellipse SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Ellipse SVG.
 * @public
 */
function Ellipse(props) {
  return createElement('ellipse', prepare(props));
}

/**
 * Return a g SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} G SVG.
 * @public
 */
function G(props) {
  const { x, y, ...rest } = props;

  if ((x || y) && !rest.translate) {
    rest.translate = `${x || 0}, ${y || 0}`;
  }

  return createElement('g', prepare(rest));
}

/**
 * Return a image SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Image SVG.
 * @public
 */
function Image(props) {
  return createElement('image', prepare(props));
}

/**
 * Return a line SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Line SVG.
 * @public
 */
function Line(props) {
  return createElement('line', prepare(props));
}

/**
 * Return a linearGradient SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} LinearGradient SVG.
 * @public
 */
function LinearGradient(props) {
  return createElement('linearGradient', prepare(props));
}

/**
 * Return a path SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Path SVG.
 * @public
 */
function Path(props) {
  return createElement('path', prepare(props));
}

/**
 * Return a polygon SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Polygon SVG.
 * @public
 */
function Polygon(props) {
  return createElement('polygon', prepare(props));
}

/**
 * Return a polyline SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Polyline SVG.
 * @public
 */
function Polyline(props) {
  return createElement('polyline', prepare(props));
}

/**
 * Return a radialGradient SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} RadialGradient SVG.
 * @public
 */
function RadialGradient(props) {
  return createElement('radialGradient', prepare(props));
}

/**
 * Return a rect SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Rect SVG.
 * @public
 */
function Rect(props) {
  return createElement('rect', prepare(props));
}

/**
 * Return a stop SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Stop SVG.
 * @public
 */
function Stop(props) {
  return createElement('stop', prepare(props));
}

/**
 * Return a SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} SVG.
 * @public
 */
function Svg(props) {
  const { title, ...rest } = props;

  if (title) {
    return createElement(
      'svg',
      { role: 'img', 'aria-label': '[title]', ...prepare(rest) },
      [createElement('title', {}, title), props.children],
    );
  }

  return createElement('svg', prepare(rest));
}

/**
 * Return a symbol SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Symbol SVG.
 * @public
 */
function Symbol(props) {
  return createElement('symbol', prepare(props));
}

/**
 * Return a text SVG element.
 *
 * @returns {React.Component} Text SVG.
 * @public
 * @param {Object} props The properties that are spread on the SVG element.
 * @param {String} props.x x position
 * @param {String} props.y y position
 * @param {String} props.dx delta x
 * @param {String} props.dy delta y
 * @param {String} props.rotate rotation
 */
function Text(props) {
  return createElement('text', prepare(props));
}

/**
 * Return a tspan SVG element.
 *
 * @returns {React.Component} TSpan SVG.
 * @public
 * @param {Object} props The properties that are spread on the SVG element.
 * @param {String} props.x x position
 * @param {String} props.y y position
 * @param {String} props.dx delta x
 * @param {String} props.dy delta y
 * @param {String} props.rotate rotation
 */
function TSpan(props) {
  return createElement('tspan', prepare(props));
}

/**
 * Return a textpath SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} TextPath SVG.
 * @public
 */
function TextPath(props) {
  return createElement('textPath', prepare(props));
}

/**
 * Return a use SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Use(props) {
  return createElement('use', prepare(props));
}

/**
 * Return a mask SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Mask(props) {
  return createElement('mask', prepare(props));
}

/**
 * Return a pattern SVG element.
 *
 * @param {Object} props The properties that are spread on the SVG element.
 * @returns {React.Component} Use SVG.
 * @public
 */
function Pattern(props) {
  return createElement('pattern', prepare(props));
}

//
// Expose everything in the same way as `react-native-svg` is doing.
//
export {
  Circle,
  ClipPath,
  Defs,
  Ellipse,
  G,
  Image,
  Line,
  LinearGradient,
  Mask,
  Path,
  Pattern,
  Polygon,
  Polyline,
  RadialGradient,
  Rect,
  Stop,
  Svg,
  Symbol,
  TSpan,
  Text,
  TextPath,
  Use,
};

export default Svg;
<|endoftext|>


<commit_before>import { createElement } from 'react-native-web';
import { resolve } from './lib/resolve';

/**
 * The `react-native-svg` has some non-standard api's that do not match with the
 * properties that can be applied to web SVG elements. This prepare function removes
 * those properties and adds the properties back in their correct location.
 *
 * @param {Object} props Properties given to us.
 * @returns {Object} Cleaned object.
 * @private
 */
function prepare(props) {
  /* eslint-disable eqeqeq */
  const {
    translate,
    scale,
    rotation,
    skewX,
    skewY,
    originX,
    originY,
    fontFamily,
    fontSize,
    fontWeight,
    fontStyle,
    style,
    ...clean
  } = props;

  const transform = [];

  if (originX != null || originY != null) {
    transform.push(`translate(${originX || 0}, ${originY || 0})`);
  }
  if (translate != null) {
    transform.push(`translate(${translate})`);
  }
  if (scale != null) {
    transform.push(`scale(${scale})`);
  }
  // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
  if (rotation != null) {
    transform.push(`rotate(${rotation})`);
  }
  if (skewX != null) {
    transform.push(`skewX(${skewX})`);
  }
  if (skewY != null) {
    transform.push(`skewY(${skewY})`);
  }
  if (originX != null || originY != null) {
    transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
  }

  if (transform.length) {
    clean.transform = transform.join(' ');
  }

  const styles = {};

  if (fontFamily != null) {
    styles.fontFamily = fontFamily;
  }
  if (fontSize != null) {
    styles.fontSize = fontSize;
  }
  if (fontWeight != null) {
    styles.fontWeight = fontWeight;
  }
  if (fontStyle != null) {
    styles.fontStyle = fontStyle;
  }

  clean.style = resolve(style, styles);

  return clean;
}

export function Circle(props) {
  return createElement('circle', prepare(props));
}

export function ClipPath(props) {
  return createElement('clipPath', prepare(props));
}

export function Defs(props) {
  return createElement('defs', prepare(props));
}

export function Ellipse(props) {
  return createElement('ellipse', prepare(props));
}

export function G(props) {
  const { x, y, ...rest } = props;

  if ((x || y) && !rest.translate) {
    rest.translate = `${x || 0}, ${y || 0}`;
  }

  return createElement('g', prepare(rest));
}

export function Image(props) {
  return createElement('image', prepare(props));
}

export function Line(props) {
  return createElement('line', prepare(props));
}

export function LinearGradient(props) {
  return createElement('linearGradient', prepare(props));
}

export function Path(props) {
  return createElement('path', prepare(props));
}

export function Polygon(props) {
  return createElement('polygon', prepare(props));
}

export function Polyline(props) {
  return createElement('polyline', prepare(props));
}

export function RadialGradient(props) {
  return createElement('radialGradient', prepare(props));
}

export function Rect(props) {
  return createElement('rect', prepare(props));
}

export function Stop(props) {
  return createElement('stop', prepare(props));
}

export function Svg(props) {
  return createElement('svg', prepare(props));
}

export function Symbol(props) {
  return createElement('symbol', prepare(props));
}

export function Text(props) {
  return createElement('text', prepare(props));
}

export function TSpan(props) {
  return createElement('tspan', prepare(props));
}

export function TextPath(props) {
  return createElement('textPath', prepare(props));
}

export function Use(props) {
  return createElement('use', prepare(props));
}

export function Mask(props) {
  return createElement('mask', prepare(props));
}

export function Pattern(props) {
  return createElement('pattern', prepare(props));
}

export default Svg;
<commit_msg>[web] Support Animated<commit_after>import { createElement } from 'react-native-web';
import { resolve } from './lib/resolve';
import { Component } from 'react';

/**
 * `react-native-svg` supports additional props that aren't defined in the spec.
 * This function replaces them in a spec conforming manner.
 *
 * @param {Object} props Properties given to us.
 * @returns {Object} Cleaned object.
 * @private
 */
function prepare(props) {
  const {
    translate,
    scale,
    rotation,
    skewX,
    skewY,
    originX,
    originY,
    fontFamily,
    fontSize,
    fontWeight,
    fontStyle,
    style,
    ...clean
  } = props;

  const transform = [];

  if (originX != null || originY != null) {
    transform.push(`translate(${originX || 0}, ${originY || 0})`);
  }
  if (translate != null) {
    transform.push(`translate(${translate})`);
  }
  if (scale != null) {
    transform.push(`scale(${scale})`);
  }
  // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
  if (rotation != null) {
    transform.push(`rotate(${rotation})`);
  }
  if (skewX != null) {
    transform.push(`skewX(${skewX})`);
  }
  if (skewY != null) {
    transform.push(`skewY(${skewY})`);
  }
  if (originX != null || originY != null) {
    transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
  }

  if (transform.length) {
    clean.transform = transform.join(' ');
  }

  const styles = {};

  if (fontFamily != null) {
    styles.fontFamily = fontFamily;
  }
  if (fontSize != null) {
    styles.fontSize = fontSize;
  }
  if (fontWeight != null) {
    styles.fontWeight = fontWeight;
  }
  if (fontStyle != null) {
    styles.fontStyle = fontStyle;
  }

  clean.style = resolve(style, styles);

  return clean;
}

export class Circle extends Component {
  render() {
    return createElement('circle', prepare(this.props));
  }
}

export class ClipPath extends Component {
  render() {
    return createElement('clipPath', prepare(this.props));
  }
}

export class Defs extends Component {
  render() {
    return createElement('defs', prepare(this.props));
  }
}

export class Ellipse extends Component {
  render() {
    return createElement('ellipse', prepare(this.props));
  }
}

export class G extends Component {
  render() {
    const { x, y, ...rest } = this.props;

    if ((x || y) && !rest.translate) {
      rest.translate = `${x || 0}, ${y || 0}`;
    }

    return createElement('g', prepare(rest));
  }
}

export class Image extends Component {
  render() {
    return createElement('image', prepare(this.props));
  }
}

export class Line extends Component {
  render() {
    return createElement('line', prepare(this.props));
  }
}

export class LinearGradient extends Component {
  render() {
    return createElement('linearGradient', prepare(this.props));
  }
}

export class Path extends Component {
  render() {
    return createElement('path', prepare(this.props));
  }
}

export class Polygon extends Component {
  render() {
    return createElement('polygon', prepare(this.props));
  }
}

export class Polyline extends Component {
  render() {
    return createElement('polyline', prepare(this.props));
  }
}

export class RadialGradient extends Component {
  render() {
    return createElement('radialGradient', prepare(this.props));
  }
}

export class Rect extends Component {
  render() {
    return createElement('rect', prepare(this.props));
  }
}

export class Stop extends Component {
  render() {
    return createElement('stop', prepare(this.props));
  }
}

export class Svg extends Component {
  render() {
    return createElement('svg', prepare(this.props));
  }
}

export class Symbol extends Component {
  render() {
    return createElement('symbol', prepare(this.props));
  }
}

export class Text extends Component {
  render() {
    return createElement('text', prepare(this.props));
  }
}

export class TSpan extends Component {
  render() {
    return createElement('tspan', prepare(this.props));
  }
}

export class TextPath extends Component {
  render() {
    return createElement('textPath', prepare(this.props));
  }
}

export class Use extends Component {
  render() {
    return createElement('use', prepare(this.props));
  }
}

export class Mask extends Component {
  render() {
    return createElement('mask', prepare(this.props));
  }
}

export class Pattern extends Component {
  render() {
    return createElement('pattern', prepare(this.props));
  }
}

export default Svg;
<|endoftext|>


<commit_before>/**
 * YouTube iframe API required setup
 */
var player;
var hasError = false;
function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
        height: '300',
        width: '400',
        // Parse the querystring and populate the video when loading the page
        videoId: getCurrentVideoID(),
        playerVars: { 'autoplay': 0, 'cc_load_policy': 0},
        events: {
            'onReady': onPlayerReady,
            'onStateChange': function onPlayerStateChange(event) {
                // Uncomment for debugging
                //console.log("State changed to " + event.data);
				var playerState = event.data;

				 if(playerState === 0)
				 {
					$("#play").toggle();
					$("#pause").toggle();
				 }
            },
            'onError': function(event) {
                var message = "Got an unknown error, check the JS console.";
                var verboseMessage = message;
                hasError = true;

                // Handle the different error codes
                switch(event.data) {
                    case 2:
                        verboseMessage = "The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.";
                        message = "looks like an invalid video ID";
                        break;
                    case 5:
                        verboseMessage = "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.";
                        message = "we can't play that video here, or something is wrong with YouTube's iframe API";
                        break;
                    case 100:
                        verboseMessage = "The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.";
                        message = "we can't find that video, it might be private or removed";
                        break;
                    case 101:
                        verboseMessage = "The owner of the requested video does not allow it to be played in embedded players.";
                        message = "the video owner won't allow us to play that video";
                        break;
                    case 150:
                        verboseMessage = "This error is the same as 101. It's just a 101 error in disguise!";
                        message = "the video owner won't allow us to play that video";
                        break;
                }

                // Update the UI w/ error
                showErrorMessage(message);
                ga("send", "event", "YouTube iframe API error", verboseMessage);

                // Log debug info
                console.log("Verbose debug error message: ", verboseMessage);
            }
        }
    });
}

function togglePlayer() {
    // TODO: google analytics
    var p = $("#player");
    p.toggle();
    if (p.is(":visible")) {
        $("#togglePlayer").text("Hide Player");
    }
    else {
        $("#togglePlayer").text("Show Player");
    }
}

function togglePlayPause() {
    // TODO: google analytics
    if ($("#play").is(":visible")) {
        player.playVideo();
        // Autoplay is disabled on mobile, double check before toggling
        setTimeout(function() {
            if (player.getPlayerState() === 1) {
                $("#play").toggle();
                $("#pause").toggle();
            }
        }, 1000);
    }
    else {
        player.pauseVideo();
        setTimeout(function() {
            if (player.getPlayerState() === 2) {
                $("#play").toggle();
                $("#pause").toggle();
            }
        }, 1000);
    }
}

// Takes seconds as a Number, returns a : delimited string
function cleanTime(time) {
    // Awesome hack for int->double cast http://stackoverflow.com/a/8388831/2785681
    var t = ~~time;

    var seconds = t % 60;
    var mins = (t - seconds) % (60 * 60) / 60;
    var hours = 0;

    var potentialHours = t - mins*60 - seconds;
    if (!isNaN(parseInt(potentialHours / 3600))) {
        hours = potentialHours / 3600;
    }

    var ret = "";
    if (hours > 0) {
        ret += hours + ":";
        if (mins < 10) {
            ret += "0";
        }
    }
    ret += mins + ":";
    if (seconds < 10) {
        ret += "0";
    }
    ret += seconds;

    return ret;
}

function updatePlayerTime() {
    $("#currentTime").text(cleanTime(player.getCurrentTime()));
    // TODO: after the video loads, player.getDuration() may have changed +/- 1
    $("#totalTime").text(cleanTime(player.getDuration()));
}

// Lock for updating the volume
var VOLUME_LOCKED = false;

function onPlayerReady(event) {
    // Only play the video if it's actually there
    if (getCurrentVideoID()) {
        $("#zen-video-error").text("");
        event.target.playVideo();
        ga("send", "event", "Playing YouTube video title", player.getVideoData().title);
        ga("send", "event", "Playing YouTube video author", player.getVideoData().author);
        ga("send", "event", "Playing YouTube video duration (seconds)", player.getDuration());
        $("#zen-video-title").html(" " + player.getVideoData().title);
        $("#zen-video-title").attr("href", player.getVideoUrl());
        togglePlayPause();

        $("#playerTime").show();

        // Update the time(s) every 100ms
        setInterval(function() {
            if (!VOLUME_LOCKED) {
                $("#volume").slider("setValue", player.getVolume());
            }
            updatePlayerTime();
        }, 100);
    }
    else {
        // Clear the now playing text
        $("#zen-video-title").text("");
        $("#playerTime").hide();
    }
}

function stopVideo() {
    player.stopVideo();
}

/**
 * Zen Audio Player functions
 */
function showErrorMessage(message) {
    $("#zen-video-error").text("ERROR: " + message);
    $("#zen-video-error").show();
}

function getParameterByName(url, name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(url);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function getCurrentVideoID() {
    var v = getParameterByName(window.location.search, "v");
    if (v.length > 0) {
        return parseYoutubeVideoID(v);
    }
    return v;
}

function makeListenURL(videoID) {
    var url = window.location.href;
    if (window.location.search.length !== 0) {
        url = window.location.href.replace(window.location.search, "");
    }
    return url + "?v=" + videoID;
}

// TODO: this function can go away, the YouTube API will let you play video by URL
// The url parameter could be the video ID
function parseYoutubeVideoID(url) {
    var videoID = null;

    var shortUrlDomain = "youtu.be";
    var longUrlDomain = "youtube.com";

    if (url && url.length > 0) {
        // youtube.com format
        if (url.indexOf(longUrlDomain) !== -1) {
            ga("send", "event", "video ID format", longUrlDomain);
            videoID = getParameterByName(url, "v");
            // If the URL had 2 v parameters, try parsing the second (usually when ?v=someurl&v=xyz)
            if (videoID === "") {
                videoID = getParameterByName(window.location.href.substring(window.location.href.indexOf(url)), "v");
            }
        }
        // youtu.be format
        else if (url.indexOf(shortUrlDomain) !== -1) {
            ga("send", "event", "video ID format", shortUrlDomain);
            var endPosition = url.indexOf("?") === -1 ? url.length : url.indexOf("?");
            var offset = url.indexOf(shortUrlDomain) + shortUrlDomain.length + 1; // Skip over the slash also
            videoID = url.substring(offset, endPosition);
        }
        // Assume YouTube video ID string
        else {
            ga("send", "event", "video ID format", "video ID");
            videoID = url;
        }

        var slashPos = videoID.indexOf("/");
        // We found a slash in the video ID (ex: real id is ABC123, but saw ABC123/zen)
        // So, only keep what's before the slash
        if (slashPos !== -1) {
            videoID = videoID.substring(0, slashPos);
        }
        return videoID;
    }
    showErrorMessage("Failed to parse the video ID.");
}

$(function() {
    var starveTheEgoFeedTheSoul_GlitchMob = "koJv-j1usoI";

    // Preload the form from the URL
    var currentVideoID = getCurrentVideoID();
    if (currentVideoID) {
        $("#v").attr("value", currentVideoID);
    }

    // Hide the demo link if playing the demo video's audio
    if (currentVideoID === starveTheEgoFeedTheSoul_GlitchMob) {
        $("#demo").hide();
    }

    // Handle form submission
    $("#form").submit(function(event) {
        event.preventDefault();
        var formValue = $("#v").val();
        if (formValue) {
            var videoID = parseYoutubeVideoID(formValue);
            ga("send", "event", "form submitted", videoID);
            window.location.href = makeListenURL(videoID);
        }
        else {
            showErrorMessage("Try entering a YouTube video ID or URL!");
        }
    });

    // Handle demo link click
    $("#demo").click(function(event) {
        event.preventDefault();
        ga("send", "event", "demo", "clicked");

        // Don't continue appending to the URL if it appears "good enough".
        // This is likely only a problem if the demo link didn't work right the first time
        if (window.location.href.indexOf(starveTheEgoFeedTheSoul_GlitchMob) === -1) {
            window.location.href = makeListenURL(starveTheEgoFeedTheSoul_GlitchMob);
        }
        else {
            ga("send", "event", "demo", "already had video ID in URL");
        }
    });

    // Initialize volume slider
    $("#volume").slider({
        min: 0,
        max: 100,
        setp: 1,
        value: 50,
        tooltip: "hide",
        id: "volumeSliderControl",
        formatter: function(){}
    });

    // Media controls
    $("#playPause").click(function(event) {
        togglePlayPause();
    });
    $("#togglePlayer").click(function(event) {
        togglePlayer();
    });

    function updateVolumeFromSlider() {
        if (player) {
            player.setVolume($("#volume").slider("getValue"));
        }
    }

    $("#volume").on("slideStart", function() {
        VOLUME_LOCKED = true;
        updateVolumeFromSlider();
    });
    $("#volume").on("change", function() {
        updateVolumeFromSlider();
    });
    $("#volume").on("slideStop", function() {
        updateVolumeFromSlider();
        VOLUME_LOCKED = false;
    });
});

/* jshint ignore:start */
// Google Analytics goodness
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-62983413-1', 'auto');
ga('send', 'pageview');
/* jshint ignore:end */
<commit_msg>Changing tabs to spaces.<commit_after>/**
 * YouTube iframe API required setup
 */
var player;
var hasError = false;
function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
        height: '300',
        width: '400',
        // Parse the querystring and populate the video when loading the page
        videoId: getCurrentVideoID(),
        playerVars: { 'autoplay': 0, 'cc_load_policy': 0},
        events: {
            'onReady': onPlayerReady,
            'onStateChange': function onPlayerStateChange(event) {
                // Uncomment for debugging
                //console.log("State changed to " + event.data);
                var playerState = event.data;

                 if(playerState === 0)
                 {
                    $("#play").toggle();
                    $("#pause").toggle();
                 }
            },
            'onError': function(event) {
                var message = "Got an unknown error, check the JS console.";
                var verboseMessage = message;
                hasError = true;

                // Handle the different error codes
                switch(event.data) {
                    case 2:
                        verboseMessage = "The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.";
                        message = "looks like an invalid video ID";
                        break;
                    case 5:
                        verboseMessage = "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.";
                        message = "we can't play that video here, or something is wrong with YouTube's iframe API";
                        break;
                    case 100:
                        verboseMessage = "The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.";
                        message = "we can't find that video, it might be private or removed";
                        break;
                    case 101:
                        verboseMessage = "The owner of the requested video does not allow it to be played in embedded players.";
                        message = "the video owner won't allow us to play that video";
                        break;
                    case 150:
                        verboseMessage = "This error is the same as 101. It's just a 101 error in disguise!";
                        message = "the video owner won't allow us to play that video";
                        break;
                }

                // Update the UI w/ error
                showErrorMessage(message);
                ga("send", "event", "YouTube iframe API error", verboseMessage);

                // Log debug info
                console.log("Verbose debug error message: ", verboseMessage);
            }
        }
    });
}

function togglePlayer() {
    // TODO: google analytics
    var p = $("#player");
    p.toggle();
    if (p.is(":visible")) {
        $("#togglePlayer").text("Hide Player");
    }
    else {
        $("#togglePlayer").text("Show Player");
    }
}

function togglePlayPause() {
    // TODO: google analytics
    if ($("#play").is(":visible")) {
        player.playVideo();
        // Autoplay is disabled on mobile, double check before toggling
        setTimeout(function() {
            if (player.getPlayerState() === 1) {
                $("#play").toggle();
                $("#pause").toggle();
            }
        }, 1000);
    }
    else {
        player.pauseVideo();
        setTimeout(function() {
            if (player.getPlayerState() === 2) {
                $("#play").toggle();
                $("#pause").toggle();
            }
        }, 1000);
    }
}

// Takes seconds as a Number, returns a : delimited string
function cleanTime(time) {
    // Awesome hack for int->double cast http://stackoverflow.com/a/8388831/2785681
    var t = ~~time;

    var seconds = t % 60;
    var mins = (t - seconds) % (60 * 60) / 60;
    var hours = 0;

    var potentialHours = t - mins*60 - seconds;
    if (!isNaN(parseInt(potentialHours / 3600))) {
        hours = potentialHours / 3600;
    }

    var ret = "";
    if (hours > 0) {
        ret += hours + ":";
        if (mins < 10) {
            ret += "0";
        }
    }
    ret += mins + ":";
    if (seconds < 10) {
        ret += "0";
    }
    ret += seconds;

    return ret;
}

function updatePlayerTime() {
    $("#currentTime").text(cleanTime(player.getCurrentTime()));
    // TODO: after the video loads, player.getDuration() may have changed +/- 1
    $("#totalTime").text(cleanTime(player.getDuration()));
}

// Lock for updating the volume
var VOLUME_LOCKED = false;

function onPlayerReady(event) {
    // Only play the video if it's actually there
    if (getCurrentVideoID()) {
        $("#zen-video-error").text("");
        event.target.playVideo();
        ga("send", "event", "Playing YouTube video title", player.getVideoData().title);
        ga("send", "event", "Playing YouTube video author", player.getVideoData().author);
        ga("send", "event", "Playing YouTube video duration (seconds)", player.getDuration());
        $("#zen-video-title").html(" " + player.getVideoData().title);
        $("#zen-video-title").attr("href", player.getVideoUrl());
        togglePlayPause();

        $("#playerTime").show();

        // Update the time(s) every 100ms
        setInterval(function() {
            if (!VOLUME_LOCKED) {
                $("#volume").slider("setValue", player.getVolume());
            }
            updatePlayerTime();
        }, 100);
    }
    else {
        // Clear the now playing text
        $("#zen-video-title").text("");
        $("#playerTime").hide();
    }
}

function stopVideo() {
    player.stopVideo();
}

/**
 * Zen Audio Player functions
 */
function showErrorMessage(message) {
    $("#zen-video-error").text("ERROR: " + message);
    $("#zen-video-error").show();
}

function getParameterByName(url, name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(url);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function getCurrentVideoID() {
    var v = getParameterByName(window.location.search, "v");
    if (v.length > 0) {
        return parseYoutubeVideoID(v);
    }
    return v;
}

function makeListenURL(videoID) {
    var url = window.location.href;
    if (window.location.search.length !== 0) {
        url = window.location.href.replace(window.location.search, "");
    }
    return url + "?v=" + videoID;
}

// TODO: this function can go away, the YouTube API will let you play video by URL
// The url parameter could be the video ID
function parseYoutubeVideoID(url) {
    var videoID = null;

    var shortUrlDomain = "youtu.be";
    var longUrlDomain = "youtube.com";

    if (url && url.length > 0) {
        // youtube.com format
        if (url.indexOf(longUrlDomain) !== -1) {
            ga("send", "event", "video ID format", longUrlDomain);
            videoID = getParameterByName(url, "v");
            // If the URL had 2 v parameters, try parsing the second (usually when ?v=someurl&v=xyz)
            if (videoID === "") {
                videoID = getParameterByName(window.location.href.substring(window.location.href.indexOf(url)), "v");
            }
        }
        // youtu.be format
        else if (url.indexOf(shortUrlDomain) !== -1) {
            ga("send", "event", "video ID format", shortUrlDomain);
            var endPosition = url.indexOf("?") === -1 ? url.length : url.indexOf("?");
            var offset = url.indexOf(shortUrlDomain) + shortUrlDomain.length + 1; // Skip over the slash also
            videoID = url.substring(offset, endPosition);
        }
        // Assume YouTube video ID string
        else {
            ga("send", "event", "video ID format", "video ID");
            videoID = url;
        }

        var slashPos = videoID.indexOf("/");
        // We found a slash in the video ID (ex: real id is ABC123, but saw ABC123/zen)
        // So, only keep what's before the slash
        if (slashPos !== -1) {
            videoID = videoID.substring(0, slashPos);
        }
        return videoID;
    }
    showErrorMessage("Failed to parse the video ID.");
}

$(function() {
    var starveTheEgoFeedTheSoul_GlitchMob = "koJv-j1usoI";

    // Preload the form from the URL
    var currentVideoID = getCurrentVideoID();
    if (currentVideoID) {
        $("#v").attr("value", currentVideoID);
    }

    // Hide the demo link if playing the demo video's audio
    if (currentVideoID === starveTheEgoFeedTheSoul_GlitchMob) {
        $("#demo").hide();
    }

    // Handle form submission
    $("#form").submit(function(event) {
        event.preventDefault();
        var formValue = $("#v").val();
        if (formValue) {
            var videoID = parseYoutubeVideoID(formValue);
            ga("send", "event", "form submitted", videoID);
            window.location.href = makeListenURL(videoID);
        }
        else {
            showErrorMessage("Try entering a YouTube video ID or URL!");
        }
    });

    // Handle demo link click
    $("#demo").click(function(event) {
        event.preventDefault();
        ga("send", "event", "demo", "clicked");

        // Don't continue appending to the URL if it appears "good enough".
        // This is likely only a problem if the demo link didn't work right the first time
        if (window.location.href.indexOf(starveTheEgoFeedTheSoul_GlitchMob) === -1) {
            window.location.href = makeListenURL(starveTheEgoFeedTheSoul_GlitchMob);
        }
        else {
            ga("send", "event", "demo", "already had video ID in URL");
        }
    });

    // Initialize volume slider
    $("#volume").slider({
        min: 0,
        max: 100,
        setp: 1,
        value: 50,
        tooltip: "hide",
        id: "volumeSliderControl",
        formatter: function(){}
    });

    // Media controls
    $("#playPause").click(function(event) {
        togglePlayPause();
    });
    $("#togglePlayer").click(function(event) {
        togglePlayer();
    });

    function updateVolumeFromSlider() {
        if (player) {
            player.setVolume($("#volume").slider("getValue"));
        }
    }

    $("#volume").on("slideStart", function() {
        VOLUME_LOCKED = true;
        updateVolumeFromSlider();
    });
    $("#volume").on("change", function() {
        updateVolumeFromSlider();
    });
    $("#volume").on("slideStop", function() {
        updateVolumeFromSlider();
        VOLUME_LOCKED = false;
    });
});

/* jshint ignore:start */
// Google Analytics goodness
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-62983413-1', 'auto');
ga('send', 'pageview');
/* jshint ignore:end */
<|endoftext|>


<commit_before>/*
 * Place "Expand All" button in each user content
 */

// User contents inserted dynamically on ajax loading
$(document).bind('wpr:onload', function () {
    $('#contentArea .userContent').each(function () {
        addExpandAllButton($(this));
    });
});

// User contents inserted dynamically on ajax paging
$(document).bind('wpr:onpage', function (event, data) {
    $(data).find('.userContent').each(function () {
        addExpandAllButton($(this));
    });
});

// Add "Expand All" button in user content
function addExpandAllButton($content) {
    if ($content.is(':empty') || $content.children('.expandAll').length > 0) {
        return; // no need to add (empty or added already)
    }

    var shouldAdd = false;
    var $fbContent = $content.closest('.fbUserContent');
    if ($fbContent.find('.fss, .see_more_link, .UFIPagerLink').length > 0) {
        shouldAdd = true;
    }
    $fbContent.find('.UFICommentLink').each(function () {
        if ($(this).closest('.UFIReplyList').children('.UFIRow').length == 1) {
            shouldAdd = true;
        }
    });

    if (shouldAdd) {
        var $element = $(''
            + 'Expand All');
        $content.prepend($element);

        $element.click(function () {
            expandAll($fbContent[0]);
            $(this).fadeOut('slow');
        });
    }
}

/**
 * WORKPLACE EXPAND ALL (全部開く君)
 *
 * @author Atsushi Kambara (@atsukanrock)
 * @license MIT License
 * @see https://sansan.facebook.com/groups/663284547154538/permalink/712895618860097/
 * @note "See More" links in user contents are supported by @rockwillj
 */
function expandAll(content) {
    var triggerEvent = function (element, event) {
        if (document.createEvent) {
            var evt = document.createEvent("HTMLEvents");
            evt.initEvent(event, true, true); // event type, bubbling, cancelable
            return element.dispatchEvent(evt);
        } else {
            var evt = document.createEventObject();
            return element.fireEvent("on" + event, evt)
        }
    };
    var intervalId = setInterval(function () {
        var seeMoreLinks = content.getElementsByClassName('fss');
        var seeMoreLinksInContent = content.getElementsByClassName('see_more_link');
        var commentLinks = content.getElementsByClassName('UFICommentLink');
        var pagerLinks = content.getElementsByClassName('UFIPagerLink');
        var triggered = false;

        for (var i = 0; i < seeMoreLinks.length; i++) {
            triggerEvent(seeMoreLinks[i], 'click');
            triggered = true;
        }
        for (var i = 0; i < seeMoreLinksInContent.length; i++) {
            if (seeMoreLinksInContent[i].closest('.text_exposed') == null) {
                // "See More" links in user contents are supported by @rockwillj
                // If the ancestor element has 'text_exposed' class, it means "See More" link is collapsed.
                // If not, it means "See More" link is expanded already and also hidden.
                triggerEvent(seeMoreLinksInContent[i], 'click');
                triggered = true;
            }
        }
        for (var i = 0; i < commentLinks.length; i++) {
            if (commentLinks[i].closest('.UFIReplyList').getElementsByClassName('UFIRow').length == 1) {
                // If the reply list is still collapsed, open it. "Hide n Replies" link appears when
                // there are a lot of replies nested below a comment and it cannot be distinguished
                // from "x replied · n Reply" link by the classes applied to it.
                triggerEvent(commentLinks[i], 'click');
                triggered = true;
            }
        }
        for (var i = 0; i < pagerLinks.length; i++) {
            triggerEvent(pagerLinks[i], 'click');
            triggered = true;
        }

        if (!triggered) {
            clearInterval(intervalId);
        }
    }, 1000);
}
<commit_msg>Fix: “Expand All” feature does not work caused by change in class name from fbUserContent to userContentWrapper<commit_after>/*
 * Place "Expand All" button in each user content
 */

// User contents inserted dynamically on ajax loading
$(document).bind('wpr:onload', function () {
    $('#contentArea .userContent').each(function () {
        addExpandAllButton($(this));
    });
});

// User contents inserted dynamically on ajax paging
$(document).bind('wpr:onpage', function (event, data) {
    $(data).find('.userContent').each(function () {
        addExpandAllButton($(this));
    });
});

// Add "Expand All" button in user content
function addExpandAllButton($content) {
    if ($content.is(':empty') || $content.children('.expandAll').length > 0) {
        return; // no need to add (empty or added already)
    }

    var shouldAdd = false;
    var $wrapper = $content.closest('.userContentWrapper');
    if ($wrapper.find('.fss, .see_more_link, .UFIPagerLink').length > 0) {
        shouldAdd = true;
    }
    $wrapper.find('.UFICommentLink').each(function () {
        if ($(this).closest('.UFIReplyList').children('.UFIRow').length == 1) {
            shouldAdd = true;
        }
    });

    if (shouldAdd) {
        var $element = $(''
            + 'Expand All');
        $content.prepend($element);

        $element.click(function () {
            expandAll($wrapper[0]);
            $(this).fadeOut('slow');
        });
    }
}

/**
 * WORKPLACE EXPAND ALL (全部開く君)
 *
 * @author Atsushi Kambara (@atsukanrock)
 * @license MIT License
 * @see https://sansan.facebook.com/groups/663284547154538/permalink/712895618860097/
 * @note "See More" links in user contents are supported by @rockwillj
 */
function expandAll(content) {
    var triggerEvent = function (element, event) {
        if (document.createEvent) {
            var evt = document.createEvent("HTMLEvents");
            evt.initEvent(event, true, true); // event type, bubbling, cancelable
            return element.dispatchEvent(evt);
        } else {
            var evt = document.createEventObject();
            return element.fireEvent("on" + event, evt)
        }
    };
    var intervalId = setInterval(function () {
        var seeMoreLinks = content.getElementsByClassName('fss');
        var seeMoreLinksInContent = content.getElementsByClassName('see_more_link');
        var commentLinks = content.getElementsByClassName('UFICommentLink');
        var pagerLinks = content.getElementsByClassName('UFIPagerLink');
        var triggered = false;

        for (var i = 0; i < seeMoreLinks.length; i++) {
            triggerEvent(seeMoreLinks[i], 'click');
            triggered = true;
        }
        for (var i = 0; i < seeMoreLinksInContent.length; i++) {
            if (seeMoreLinksInContent[i].closest('.text_exposed') == null) {
                // "See More" links in user contents are supported by @rockwillj
                // If the ancestor element has 'text_exposed' class, it means "See More" link is collapsed.
                // If not, it means "See More" link is expanded already and also hidden.
                triggerEvent(seeMoreLinksInContent[i], 'click');
                triggered = true;
            }
        }
        for (var i = 0; i < commentLinks.length; i++) {
            if (commentLinks[i].closest('.UFIReplyList').getElementsByClassName('UFIRow').length == 1) {
                // If the reply list is still collapsed, open it. "Hide n Replies" link appears when
                // there are a lot of replies nested below a comment and it cannot be distinguished
                // from "x replied · n Reply" link by the classes applied to it.
                triggerEvent(commentLinks[i], 'click');
                triggered = true;
            }
        }
        for (var i = 0; i < pagerLinks.length; i++) {
            triggerEvent(pagerLinks[i], 'click');
            triggered = true;
        }

        if (!triggered) {
            clearInterval(intervalId);
        }
    }, 1000);
}
<|endoftext|>


<commit_before>var width = 960,
    height = 500;

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(200)
    .size([width, height]);

var forceSvg = d3.select("div#chord_chart").append("svg")
    .attr("width", width)
    .attr("height", height);

d3.json("newsGraph.json", function(error, graph) {
  testGraph = JSON.parse(JSON.stringify(graph))
  console.log(testGraph);

  selectedList = [0,10,2,3,4,50,60,75,18];
  selectedData1 = $.map(testGraph.nodes, function(element){
      return ($.inArray(element.id,selectedList)>-1?element:null);
    });
  finalData = {};
  finalData['nodes'] = selectedData1;
  finalData['links'] = testGraph.links;
  selectedData2 = $.map(finalData.links, function(element){
      return ($.inArray(element.source,selectedList)>-1?element:null);
    });
  console.log(selectedData2);

  selectedData3 = $.map(selectedData2, function(element){
      return ($.inArray(element.target,selectedList)>-1?element:null);
    });
  selectedData3 = selectedData3.filter(function(d){ return d.source != "undefined"; });
  console.log(selectedData3);
  new_graph = {};
  new_graph['nodes'] = selectedData1;
  new_graph['links'] = selectedData3;

  console.log(new_graph);
  drawGraph(new_graph);

  function drawGraph(graph){
    force
      .nodes(graph.nodes)
      .links(graph.links)
      .start();

    var graphMin = d3.min(graph.links, function(d){ return d.size; });
    var graphMax = d3.max(graph.links, function(d){ return d.size; });

    var fill = d3.scale.ordinal()
        .domain([graphMin, graphMax])
        .range(["#DB704D", "#D2D0C6", "#ECD08D", "#F8EDD3"]);

    var link = forceSvg.selectAll(".link")
        .data(graph.links)
      .enter().append("line")
        .attr("class", "link")
        .style('stroke-width', 1)
        .style('stroke', function(d){ return fill(d.value); })
        //.style("stroke-width", function(d) { return Math.sqrt(d.value); });

    var node = forceSvg.selectAll(".node")
        .data(graph.nodes)
      .enter().append("circle")
        .attr("class", "node")
        .attr("r", 5)
        .style("fill", function(d) { return color(d.group); })
        .call(force.drag);

    node.append("title")
        .text(function(d) { return d.name; });

    force.on("tick", function() {
      link.attr("x1", function(d) { return d.source.x; })
          .attr("y1", function(d) { return d.source.y; })
          .attr("x2", function(d) { return d.target.x; })
          .attr("y2", function(d) { return d.target.y; });

      node.attr("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; });
    });
  }

});

<commit_msg>update data<commit_after>var width = 960,
    height = 500;

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(200)
    .size([width, height]);

var forceSvg = d3.select("div#chord_chart").append("svg")
    .attr("width", width)
    .attr("height", height);

d3.json("newsGraph.json", function(error, graph) {
  testGraph = JSON.parse(JSON.stringify(graph))
  console.log(testGraph);

  selectedList = [0,10,2,3,4,50,60,75,18];
  selectedData1 = $.map(testGraph.nodes, function(element){
      return ($.inArray(element.id,selectedList)>-1?element:null);
    });
  finalData = {};
  finalData['nodes'] = selectedData1;
  finalData['links'] = testGraph.links;
  selectedData2 = $.map(finalData.links, function(element){
      return ($.inArray(element.source,selectedList)>-1?element:null);
    });
  console.log(selectedData2);

  selectedData3 = $.map(selectedData2, function(element){
      return ($.inArray(element.target,selectedList)>-1?element:null);
    });
  selectedData3 = selectedData3.filter(function(d){ return d.source == null; });
  console.log(selectedData3);
  new_graph = {};
  new_graph['nodes'] = selectedData1;
  new_graph['links'] = selectedData3;

  console.log(new_graph);
  drawGraph(new_graph);

  function drawGraph(graph){
    force
      .nodes(graph.nodes)
      .links(graph.links)
      .start();

    var graphMin = d3.min(graph.links, function(d){ return d.size; });
    var graphMax = d3.max(graph.links, function(d){ return d.size; });

    var fill = d3.scale.ordinal()
        .domain([graphMin, graphMax])
        .range(["#DB704D", "#D2D0C6", "#ECD08D", "#F8EDD3"]);

    var link = forceSvg.selectAll(".link")
        .data(graph.links)
      .enter().append("line")
        .attr("class", "link")
        .style('stroke-width', 1)
        .style('stroke', function(d){ return fill(d.value); })
        //.style("stroke-width", function(d) { return Math.sqrt(d.value); });

    var node = forceSvg.selectAll(".node")
        .data(graph.nodes)
      .enter().append("circle")
        .attr("class", "node")
        .attr("r", 5)
        .style("fill", function(d) { return color(d.group); })
        .call(force.drag);

    node.append("title")
        .text(function(d) { return d.name; });

    force.on("tick", function() {
      link.attr("x1", function(d) { return d.source.x; })
          .attr("y1", function(d) { return d.source.y; })
          .attr("x2", function(d) { return d.target.x; })
          .attr("y2", function(d) { return d.target.y; });

      node.attr("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; });
    });
  }

});

<|endoftext|>


<commit_before>// Copyright 2017-2022, University of Colorado Boulder

/**
 * Runs the lint rules on the specified files.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

// modules
const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const { ESLint } = require( 'eslint' ); // eslint-disable-line require-statement-match
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const crypto = require( 'crypto' );
const chipAway = require( './chipAway' );
// constants
const EXCLUDE_PATTERNS = [ // patterns that have no code that should be linted

  '../babel',
  '../decaf',
  '../phet-android-app',
  '../phet-info',
  '../phet-io-client-guides',
  '../phet-io-website',
  '../phet-io-wrapper-arithmetic',
  '../phet-io-wrapper-hookes-law-energy',
  '../phet-ios-app',
  '../qa',
  '../smithers',
  '../tasks'
];

const defaultLintOptions = {
  cache: true,
  format: false, // append an extra set of rules for formatting code.
  fix: false, // whether fixes should be written to disk
  warn: true, // whether errors should reported with grunt.warn
  chipAway: false, // returns responsible dev info for easier chipping.
  silent: false // don't log anything, just return the results.
};

/**
 * Use the lint results to output the desired content.
 * @param results - see ESLint.lintFiles
 * @param options - see defaultLintOptions
 * @returns {Promise<void>}
 */
const processResults = async ( results, options ) => {

  // Not all of these options are used in this function, but it seemed easier and more maintainable to just have one
  // place to state defaults.
  options = _.extend( defaultLintOptions, options );

  // Parse the results.
  const totalWarnings = _.sum( results.map( result => result.warningCount ) );
  const totalErrors = _.sum( results.map( result => result.errorCount ) );

  // Output results on errors.
  if ( totalWarnings + totalErrors > 0 ) {
    const formatter = await new ESLint().loadFormatter( 'stylish' );
    const resultText = formatter.format( results );
    !options.silent && console.log( resultText );

    // The chip-away option provides a quick and easy method to assign devs to their respective repositories.
    // Check ./chipAway.js for more information.
    if ( options.chipAway ) {
      const message = chipAway( results );
      !options.silent && console.log( 'Results from chipAway: \n' + message );
    }

    options.warn && grunt.fail.warn( `${totalErrors} errors and ${totalWarnings} warnings` );
  }
};

/**
 * Lints the specified repositories.
 * @public
 *
 * @returns {Promise} - results from linting files, see ESLint.lintFiles
 */
const lint = async ( patterns, options ) => {

  options = _.assignIn( defaultLintOptions, options );

  // filter out all unlintable pattern. An unlintable repo is one that has no `js` in it, so it will fail when trying to
  // lint it.  Also, if the user doesn't have some repos checked out, those should be skipped
  patterns = patterns.filter( pattern => !EXCLUDE_PATTERNS.includes( pattern ) &&
                                         fs.existsSync( pattern ) );

  const hash = crypto.createHash( 'md5' )
    .update( patterns.join( ',' ) )
    .digest( 'hex' );

  const eslintConfig = {

    // optional auto-fix
    fix: options.fix,

    // Caching only checks changed files or when the list of rules is changed.  Changing the implementation of a
    // custom rule does not invalidate the cache.  Caches are declared in .eslintcache files in the directory where
    // grunt was run from.
    cache: options.cache,

    // Where to store the target-specific cache file
    cacheLocation: `../chipper/eslint/cache/${hash}.eslintcache`,

    ignorePath: '../chipper/eslint/.eslintignore',

    resolvePluginsRelativeTo: '../chipper/',

    // Our custom rules live here
    rulePaths: [ '../chipper/eslint/rules' ],

    extensions: [ '.js', '.jsx', '.ts', '.tsx' ]
  };

  const config = {};
  const configExtends = [];
  if ( options.format ) {
    configExtends.push( '../chipper/eslint/format_eslintrc.js' );
  }

  config.extends = configExtends;
  eslintConfig.baseConfig = config;

  const eslint = new ESLint( eslintConfig );

  grunt.verbose.writeln( `linting: ${patterns.join( ', ' )}` );

  // 2. Lint files. This doesn't modify target files.
  const results = await eslint.lintFiles( patterns );

  // 3. Modify the files with the fixed code.
  if ( options.fix ) {
    await ESLint.outputFixes( results );
  }

  await processResults( results, options );

  return results;
};

// @public
lint.processResults = processResults;

// Mark the version so that the pre-commit hook will only try to use the promise-based API, this means
// it won't run lint precommit hook on SHAs before the promise-based API
lint.chipperAPIVersion = 'promises1';

module.exports = lint;<commit_msg>use a single linting function, remove unused options "patterns" and "warn", https://github.com/phetsims/chipper/issues/1286<commit_after>// Copyright 2017-2022, University of Colorado Boulder

/**
 * Runs the lint rules on the specified files.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

// modules
const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const { ESLint } = require( 'eslint' ); // eslint-disable-line require-statement-match
const fs = require( 'fs' );
const chipAway = require( './chipAway' );
const showCommandLineProgress = require( '../common/showCommandLineProgress' );

// constants
const EXCLUDE_REPOS = [
  'babel',
  'decaf',
  'phet-android-app',
  'phet-info',
  'phet-io-client-guides',
  'phet-io-website',
  'phet-io-wrapper-arithmetic',
  'phet-io-wrapper-hookes-law-energy',
  'phet-ios-app',
  'qa',
  'smithers',
  'tasks'
];

// "Pattern" is really a path, we assume here that gruntfiles help keep the right directory stucture and can just pop
// out of the repo running the command
const repoToPattern = repo => `../${repo}`;

/**
 * Create an ESLint client and lint a single repo
 * @param {string} repo
 * @param {Object} [options]
 * @returns {Promise<Object>} - results from linting files, see ESLint.lintFiles
 */
const lintOneRepo = async ( repo, options ) => {

  options = _.extend( {
    cache: true,
    fix: false
  }, options );

  const eslintConfig = {

    // optional auto-fix
    fix: options.fix,

    // Caching only checks changed files or when the list of rules is changed.  Changing the implementation of a
    // custom rule does not invalidate the cache.  Caches are declared in .eslintcache files in the directory where
    // the process was run from.
    cache: options.cache,

    // Where to store the target-specific cache file
    cacheLocation: `../chipper/eslint/cache/${repo}.eslintcache`,

    ignorePath: '../chipper/eslint/.eslintignore',

    resolvePluginsRelativeTo: '../chipper/',

    // Our custom rules live here
    rulePaths: [ '../chipper/eslint/rules' ],

    extensions: [ '.js', '.jsx', '.ts', '.tsx' ]
  };

  const config = {};
  const configExtends = [];
  if ( options.format ) {
    configExtends.push( '../chipper/eslint/format_eslintrc.js' );
  }

  config.extends = configExtends;
  eslintConfig.baseConfig = config;

  const eslint = new ESLint( eslintConfig );

  return eslint.lintFiles( repoToPattern( repo ) );
};

/**
 * Lints the specified repositories.
 * @public
 *
 * @param {string[]} repos - list of repos to lint
 * @param {Object} [options]
 * @returns {Promise<Object>} - results from linting files, see ESLint.lintFiles
 */
const lint = async ( repos, options ) => {

  options = _.extend( {
    cache: true,
    format: false, // append an extra set of rules for formatting code.
    fix: false, // whether fixes should be written to disk
    chipAway: false, // returns responsible dev info for easier chipping.
    showProgressBar: true
  }, options );

  // filter out all unlintable repos. An unlintable repo is one that has no `js` in it, so it will fail when trying to
  // lint it.  Also, if the user doesn't have some repos checked out, those should be skipped
  repos = repos.filter( repo => !EXCLUDE_REPOS.includes( repo ) &&
                                fs.existsSync( repoToPattern( repo ) ) );

  const allResults = [];
  for ( let i = 0; i < repos.length; i++ ) {
    options.showProgressBar && showCommandLineProgress( i / repos.length, false );

    const results = await lintOneRepo( repos[ i ], {
      cache: options.cache,
      format: options.format,
      chipAway: false, // silence individual repo reporting
      silent: true // silence individual repo reporting
    } );

    // MK found that results are unique to a file, so this seemed safe and was working well.
    allResults.push( ...results );
  }

  options.showProgressBar && showCommandLineProgress( 1, true );

  // 3. Modify the files with the fixed code.
  if ( options.fix ) {
    await ESLint.outputFixes( allResults );
  }

  // Parse the results.
  const totalWarnings = _.sum( allResults.map( result => result.warningCount ) );
  const totalErrors = _.sum( allResults.map( result => result.errorCount ) );

  // Output results on errors.
  if ( totalWarnings + totalErrors > 0 ) {

    // No need to have the same ESLint just to format
    const formatter = await new ESLint().loadFormatter( 'stylish' );
    const resultText = formatter.format( allResults );
    console.log( resultText );

    // The chip-away option provides a quick and easy method to assign devs to their respective repositories.
    // Check ./chipAway.js for more information.
    if ( options.chipAway ) {
      const message = chipAway( allResults );
      console.log( 'Results from chipAway: \n' + message );
    }
  }

  return allResults;
};

// Mark the version so that the pre-commit hook will only try to use the promise-based API, this means
// it won't run lint precommit hook on SHAs before the promise-based API
lint.chipperAPIVersion = 'promises1';

module.exports = lint;<|endoftext|>