<commit_before>import { assign, isFunction, defaults, isEmpty, fromPairs } from "lodash";
import React from "react";
import PropTypes from "prop-types";
import CustomPropTypes from "../victory-util/prop-types";
import Events from "../victory-util/events";
import Helpers from "../victory-util/helpers";
import Timer from "../victory-util/timer";

export default class VictorySharedEvents extends React.Component {
  static displayName = "VictorySharedEvents";

  static role = "shared-event-wrapper";

  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.node),
      PropTypes.node
    ]),
    container: PropTypes.node,
    eventKey: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.func,
      CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
      PropTypes.string
    ]),
    events: PropTypes.arrayOf(PropTypes.shape({
      childName: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ]),
      eventHandlers: PropTypes.object,
      eventKey: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.func,
        CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
        PropTypes.string
      ]),
      target: PropTypes.string
    })),
    externalEventMutations: PropTypes.arrayOf(PropTypes.shape({
      callback: PropTypes.function,
      childName: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ]),
      eventKey: PropTypes.oneOfType([
        PropTypes.array,
        CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
        PropTypes.string
      ]),
      mutation: PropTypes.function,
      target: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ])
    })),
    groupComponent: PropTypes.node
  };

  static defaultProps = {
    groupComponent: <g/>
  };

  static contextTypes = {
    getTimer: PropTypes.func
  };

  static childContextTypes = {
    getTimer: PropTypes.func
  };

  constructor(props) {
    super(props);
    this.state = this.state || {};
    this.getScopedEvents = Events.getScopedEvents.bind(this);
    this.getEventState = Events.getEventState.bind(this);
    this.getTimer = this.getTimer.bind(this);
    this.setUpChildren(props);
  }

  getChildContext() {
    return {
      getTimer: this.getTimer
    };
  }

  componentWillReceiveProps(newProps) {
    this.setUpChildren(newProps);
  }

  getTimer() {
    if (this.context.getTimer) {
      return this.context.getTimer();
    }
    if (!this.timer) {
      this.timer = new Timer();
    }
    return this.timer;
  }

  getAllEvents(props) {
    const components = ["container", "groupComponent"];
    this.componentEvents = Events.getComponentEvents(props, components);
    if (Array.isArray(this.componentEvents)) {
      return Array.isArray(props.events) ?
        this.componentEvents.concat(...props.events) : this.componentEvents;
    }
    return props.events;
  }

  setUpChildren(props) {
    this.events = this.getAllEvents(props);
    const { externalEventMutations, container, children } = props;
    if (this.events || !isEmpty(externalEventMutations)) {
      this.childComponents = React.Children.toArray(children);
      const childBaseProps = this.getBasePropsFromChildren(this.childComponents);
      const parentBaseProps = container ? container.props : {};
      const childNames = Object.keys(childBaseProps);
      this.baseProps = assign({}, childBaseProps, { parent: parentBaseProps });

      if (!isEmpty(externalEventMutations)) {
        const externalMutations = Events.getExternalMutationsWithChildren(
          externalEventMutations, this.baseProps, this.state, childNames
        );
        const callbacks = externalEventMutations.reduce((memo, mutation) => {
          memo = isFunction(mutation.callback) ? memo.concat(mutation.callback) : memo;
          return memo;
        }, []);
        const compiledCallbacks = callbacks.length ?
          () => { callbacks.forEach((c) => c()); } : undefined;
        this.setState(externalMutations, compiledCallbacks);
      }
    }
  }

  getBasePropsFromChildren(childComponents) {
    const iteratee = (child, childName, parent) => {
      if (child.type && isFunction(child.type.getBaseProps)) {
        child = parent ? React.cloneElement(child, parent.props) : child;
        const baseProps = child.props && child.type.getBaseProps(child.props);
        return baseProps ? [[childName, baseProps]] : null;
      } else {
        return null;
      }
    };

    const baseProps = Helpers.reduceChildren(childComponents, iteratee);
    return fromPairs(baseProps);
  }

  getNewChildren(props) {
    const { events, eventKey } = props;
    const childNames = Object.keys(this.baseProps);
    const alterChildren = (children) => {
      return children.reduce((memo, child, index) => {
        if (child.props.children) {
          return memo.concat(React.cloneElement(
            child,
            child.props,
            alterChildren(React.Children.toArray(child.props.children))
          ));
        } else if (child.type && isFunction(child.type.getBaseProps)) {
          const name = child.props.name || childNames.shift() || index;
          const childEvents = Array.isArray(events) &&
            events.filter((event) => {
              if (event.target === "parent") {
                return false;
              }
              return Array.isArray(event.childName) ?
                event.childName.indexOf(name) > -1 :
                event.childName === name || event.childName === "all";
            });
          const sharedEvents = {
            events: childEvents,
            // partially apply child name and baseProps,
            getEvents: (evts, target) => this.getScopedEvents(evts, target, name, this.baseProps),
            // partially apply child name
            getEventState: (key, target) => this.getEventState(key, target, name)
          };
          return memo.concat(React.cloneElement(child, assign(
            { key: `events-${name}`, sharedEvents, eventKey, name },
            child.props
          )));
        } else {
          return memo.concat(child);
        }
      }, []);
    };

    return alterChildren(this.childComponents);
  }

  getContainer(props, children) {
    const parents = Array.isArray(this.events) &&
      this.events.filter((event) => event.target === "parent");
    const sharedEvents = parents.length > 0 ?
    {
      events: parents,
      // partially apply childName (null) and baseProps,
      getEvents: (evts, target) => this.getScopedEvents(evts, target, null, this.baseProps),
      getEventState: this.getEventState
    } : null;
    const container = this.props.container || this.props.groupComponent;
    const role = container.type && container.type.role;
    const containerProps = container.props || {};
    const boundGetEvents = Events.getEvents.bind(this);
    const parentEvents = sharedEvents && boundGetEvents({ sharedEvents }, "parent");
    const parentProps = defaults(
      {},
      this.getEventState("parent", "parent"),
      containerProps,
      this.baseProps.parent,
      { children }
    );
    const events = defaults(
      {}, Events.getPartialEvents(parentEvents, "parent", parentProps), containerProps.events
    );
    return role === "container" ?
      React.cloneElement(container, assign({}, parentProps, { events })) :
      React.cloneElement(container, events, children);
  }

  render() {
    if (this.events) {
      const children = this.getNewChildren(this.props);
      return this.getContainer(this.props, children);
    }
    return React.cloneElement(this.props.container, { children: this.props.children });
  }
}
<commit_msg>simplify victory-shared-events<commit_after>import { assign, isFunction, defaults, isEmpty, fromPairs } from "lodash";
import React from "react";
import PropTypes from "prop-types";
import CustomPropTypes from "../victory-util/prop-types";
import Events from "../victory-util/events";
import Helpers from "../victory-util/helpers";
import Timer from "../victory-util/timer";

export default class VictorySharedEvents extends React.Component {
  static displayName = "VictorySharedEvents";

  static role = "shared-event-wrapper";

  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.node),
      PropTypes.node
    ]),
    container: PropTypes.node,
    eventKey: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.func,
      CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
      PropTypes.string
    ]),
    events: PropTypes.arrayOf(PropTypes.shape({
      childName: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ]),
      eventHandlers: PropTypes.object,
      eventKey: PropTypes.oneOfType([
        PropTypes.array,
        PropTypes.func,
        CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
        PropTypes.string
      ]),
      target: PropTypes.string
    })),
    externalEventMutations: PropTypes.arrayOf(PropTypes.shape({
      callback: PropTypes.function,
      childName: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ]),
      eventKey: PropTypes.oneOfType([
        PropTypes.array,
        CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
        PropTypes.string
      ]),
      mutation: PropTypes.function,
      target: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array
      ])
    })),
    groupComponent: PropTypes.node
  };

  static defaultProps = {
    groupComponent: <g/>
  };

  static contextTypes = {
    getTimer: PropTypes.func
  };

  static childContextTypes = {
    getTimer: PropTypes.func
  };

  constructor(props) {
    super(props);
    this.state = this.state || {};
    this.getScopedEvents = Events.getScopedEvents.bind(this);
    this.getEventState = Events.getEventState.bind(this);
    this.getTimer = this.getTimer.bind(this);
    this.baseProps = this.getBaseProps(props);
  }

  getChildContext() {
    return {
      getTimer: this.getTimer
    };
  }

  componentWillReceiveProps(newProps) {
    this.baseProps = this.getBaseProps(newProps);
    const externalMutations = this.getExternalMutations(newProps, this.baseProps);
    this.applyExternalMutations(newProps, externalMutations);
  }

  getTimer() {
    if (this.context.getTimer) {
      return this.context.getTimer();
    }
    if (!this.timer) {
      this.timer = new Timer();
    }
    return this.timer;
  }

  getAllEvents(props) {
    const components = ["container", "groupComponent"];
    const componentEvents = Events.getComponentEvents(props, components);
    if (Array.isArray(componentEvents)) {
      return Array.isArray(props.events) ?
        componentEvents.concat(...props.events) : componentEvents;
    }
    return props.events;
  }

  applyExternalMutations(props, externalMutations) {
    if (!isEmpty(externalMutations)) {
      const callbacks = props.externalEventMutations.reduce((memo, mutation) => {
        memo = isFunction(mutation.callback) ? memo.concat(mutation.callback) : memo;
        return memo;
      }, []);
      const compiledCallbacks = callbacks.length ?
        () => { callbacks.forEach((c) => c()); } : undefined;
      this.setState(externalMutations, compiledCallbacks);
    }
  }

  getExternalMutations(props, baseProps) {
    return !isEmpty(props.externalEventMutations) ?
      Events.getExternalMutationsWithChildren(
        props.externalEventMutations, baseProps, this.state, Object.keys(baseProps)
      ) :
      undefined;
  }

  getBaseProps(props) {
    const { container } = props;
    const children = React.Children.toArray(this.props.children);
    const childBaseProps = this.getBasePropsFromChildren(children);
    const parentBaseProps = container ? container.props : {};
    return assign({}, childBaseProps, { parent: parentBaseProps });
  }

  getBasePropsFromChildren(childComponents) {
    const iteratee = (child, childName, parent) => {
      if (child.type && isFunction(child.type.getBaseProps)) {
        child = parent ? React.cloneElement(child, parent.props) : child;
        const baseProps = child.props && child.type.getBaseProps(child.props);
        return baseProps ? [[childName, baseProps]] : null;
      } else {
        return null;
      }
    };

    const baseProps = Helpers.reduceChildren(childComponents, iteratee);
    return fromPairs(baseProps);
  }

  getNewChildren(props, baseProps) {
    const { events, eventKey } = props;
    const childNames = Object.keys(baseProps);
    const alterChildren = (children) => {
      return children.reduce((memo, child, index) => {
        if (child.props.children) {
          return memo.concat(React.cloneElement(
            child,
            child.props,
            alterChildren(React.Children.toArray(child.props.children))
          ));
        } else if (child.type && isFunction(child.type.getBaseProps)) {
          const name = child.props.name || childNames.shift() || index;
          const childEvents = Array.isArray(events) &&
            events.filter((event) => {
              if (event.target === "parent") {
                return false;
              }
              return Array.isArray(event.childName) ?
                event.childName.indexOf(name) > -1 :
                event.childName === name || event.childName === "all";
            });
          const sharedEvents = {
            events: childEvents,
            // partially apply child name and baseProps,
            getEvents: (evts, target) => this.getScopedEvents(evts, target, name, baseProps),
            // partially apply child name
            getEventState: (key, target) => this.getEventState(key, target, name)
          };
          return memo.concat(React.cloneElement(child, assign(
            { key: `events-${name}`, sharedEvents, eventKey, name },
            child.props
          )));
        } else {
          return memo.concat(child);
        }
      }, []);
    };
    const childComponents = React.Children.toArray(props.children);
    return alterChildren(childComponents);
  }

  getContainer(props, baseProps, events) {
    const children = this.getNewChildren(props, baseProps);
    const parents = Array.isArray(events) &&
      events.filter((event) => event.target === "parent");
    const sharedEvents = parents.length > 0 ?
    {
      events: parents,
      // partially apply childName (null) and baseProps,
      getEvents: (evts, target) => this.getScopedEvents(evts, target, null, baseProps),
      getEventState: this.getEventState
    } : null;
    const container = props.container || props.groupComponent;
    const role = container.type && container.type.role;
    const containerProps = container.props || {};
    const boundGetEvents = Events.getEvents.bind(this);
    const parentEvents = sharedEvents && boundGetEvents({ sharedEvents }, "parent");
    const parentProps = defaults(
      {},
      this.getEventState("parent", "parent"),
      containerProps,
      baseProps.parent,
      { children }
    );
    const containerEvents = defaults(
      {}, Events.getPartialEvents(parentEvents, "parent", parentProps), containerProps.events
    );
    return role === "container" ?
      React.cloneElement(container, assign({}, parentProps, { events: containerEvents })) :
      React.cloneElement(container, containerEvents, children);
  }

  render() {
    const events = this.getAllEvents(this.props);
    if (events) {
      return this.getContainer(this.props, this.baseProps, events);
    }
    return React.cloneElement(this.props.container, { children: this.props.children });
  }
}
<|endoftext|>


<commit_before>/* global workbox:true */
/* eslint no-undef: "error" */
"use strict";

/**
 * Configuration & setup
 * - Set name for precache & runtime cache
 * - Skip waiting and claim clients so we take over from any old workers
 * - Also make sure that if we are an old worker, we force reload the page to get freshest stuff
 */
const staticCacheName = "static",
  vendorCacheName = "vendor-static",
  imageCacheName = "images",
  offlinePage = "/503.html",
  genericErrorPage = "/500.html";

workbox.core.setCacheNameDetails({
  precache: "precache",
  prefix: "dg",
  suffix: "prod",
  runtime: "site"
});
workbox.skipWaiting();
workbox.clientsClaim();

var isRefreshing = false;

var refreshWindow = function() {
  if (isRefreshing) {
    return;
  }
  isRefreshing = true;
  window.location.reload();
};

self.addEventListener("controllerchange", refreshWindow);

/**
 * Enables navigation preload which we use in our default handler
 */
workbox.navigationPreload.enable();

/**
 * Precaching all the things!
 * - All data is specified in _config.yml
 * - This empty array will be filled out via jekyll
 */
workbox.precaching.precacheAndRoute([]);

/**
 * Routing for different filetypes & endpoints
 * - Force all analytics calls to network only
 * - Force all p.gif calls (Adobe's check to see if you can use the font) to
 *   network only. If this is cached, the cache grows and grows
 * - Cache the actual font files (use.typekit.net/af) for 120 days
 * - Cache fonts stylesheets for one day
 * - Cache js, css, and json for six hours
 * - Cache up to 60 images for 30 days
 */

const vendorRouteConfiguration = function(hours) {
  return {
    cacheName: vendorCacheName,
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxAgeSeconds: 60 * 60 * hours
      })
    ]
  };
};

workbox.routing.registerRoute(
  /^https?:\/\/www\.google-analytics\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/*google*\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net\/p\.gif/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net\/af/,
  workbox.strategies.cacheFirst(vendorRouteConfiguration(24 * 120))
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net/,
  workbox.strategies.cacheFirst(vendorRouteConfiguration(24))
);
workbox.routing.registerRoute(
  /.*\.(?:js|css|json)$/,
  workbox.strategies.cacheFirst({
    cacheName: staticCacheName,
    plugins: [
      new workbox.expiration.Plugin({
        maxAgeSeconds: 60 * 60 * 6
      })
    ]
  })
);
workbox.routing.registerRoute(
  /\.(?:png|gif|jpg|jpeg|svg|webp|ico)$/,
  workbox.strategies.cacheFirst({
    cacheName: imageCacheName,
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 60,
        maxAgeSeconds: 60 * 60 * 24 * 30
      })
    ]
  })
);

/**
 * Returns a generic error page from cache, replacing the error content with
 * status and the actual page URL.
 */
const errorPageForURL = async(pageURL) => {
  const cachedResponse = await caches.match(genericErrorPage);

  return responseWithReplacedErrorContent(cachedResponse, pageURL, 500);
};

/**
 * Returns the offline page from cache, replacing the error content with a 503
 * status and the actual page URL.
 */
const offlinePageForURL = async(pageURL) => {
  const cachedResponse = await caches.match(offlinePage);

  return responseWithReplacedErrorContent(cachedResponse, pageURL, 503);
};

/**
 * Takes an existing response and replaces some error content. Specifically:
 * - Replaces "<error>"" or "</error>" with the actual status code
 * - Replaces "? page" with the actual pageURL
 *
 * Note that the status code falls back to the response's status if not defined.
 */
function responseWithReplacedErrorContent(existingResponse, pageURL, status) {
  if (!status) {
    status = existingResponse.status;
  }

  const constructedResponse = {
    status: status,
    statusText: existingResponse.statusText,
    headers: existingResponse.headers
  };

  // Replace the requisite strings in the page with the real URL and status.
  return existingResponse.text().then(function(body) {
    const left = "<";
    const right = status + ">";

    var newContent = body.replace(/\? page/, pageURL);
    newContent = newContent.replace(/<error>/, left + right);
    newContent = newContent.replace(/<\/error>/, left + "/" + right);

    return new Response(newContent, constructedResponse);
  });
}

/**
 * Handles a standard network response. There are two main components here:
 * - If the response 404'd, we need to replace the placeholder error content
 * - Otherwise, check if we're requesting a document
 * - If a document, return the response or fall back to the offline page
 * - Otherwise, just return the response
 */
function handleResponse(response, pageURL, destination) {
  // If the response 404'd, we should replace the page URL and return it
  if (response && response.status === 404) {
    return responseWithReplacedErrorContent(response, pageURL);
  }

  // Check if we should try returning offline page if response fails
  switch (destination) {
    case "document":
      return response || offlinePageForURL(pageURL);
    default:
      return response;
  }
}

/**
 * Fetches a page, doing all the work to handle edge cases. Specifically:
 * - For not-GET methods, uses networkOnly to not cache them
 * - If there's a preload response, just use that
 * - Everything else is run through handleResponse with networkFirst strategy
 */
const fetchPage = async(context) => {
  const nonGetStrategy = workbox.strategies.networkOnly();

  // If it's non GET request, use networkOnly
  if (context.event.request.method !== "GET") {
    return nonGetStrategy.handle(context);
  }

  // Use the preloaded response, if it's there
  const preloadResponse = await context.preloadResponse;

  if (preloadResponse) {
    return preloadResponse;
  }

  // Otherwise, fetch and handle the response
  const pageURL = context.event.request.url;
  const destination = context.event.request.destination;
  const defaultStrategy = workbox.strategies.networkFirst();
  const response = await defaultStrategy.handle(context);

  return handleResponse(response, pageURL, destination);
};

/**
 * If it's a document, it fetches the fallback error page if possible, or
 * returns a generic response error if not.
 */
function fetchErrorPage(context) {
  const pageURL = context.event.request.url;
  const destination = context.event.request.destination;

  switch (destination) {
    case "document":
      return errorPageForURL(pageURL);
    default:
      return Response.error();
  }
}

workbox.routing.setDefaultHandler(fetchPage);
workbox.routing.setCatchHandler(fetchErrorPage);
<commit_msg>Network onlys more endpoints (39MB -> 600kb cache)<commit_after>/* global workbox:true */
/* eslint no-undef: "error" */
"use strict";

/**
 * Configuration & setup
 * - Set name for precache & runtime cache
 * - Skip waiting and claim clients so we take over from any old workers
 * - Also make sure that if we are an old worker, we force reload the page to get freshest stuff
 */
const staticCacheName = "static",
  vendorCacheName = "vendor-static",
  imageCacheName = "images",
  offlinePage = "/503.html",
  genericErrorPage = "/500.html";

workbox.core.setCacheNameDetails({
  precache: "precache",
  prefix: "dg",
  suffix: "prod",
  runtime: "site"
});
workbox.skipWaiting();
workbox.clientsClaim();

var isRefreshing = false;

var refreshWindow = function() {
  if (isRefreshing) {
    return;
  }
  isRefreshing = true;
  window.location.reload();
};

self.addEventListener("controllerchange", refreshWindow);

/**
 * Enables navigation preload which we use in our default handler
 */
workbox.navigationPreload.enable();

/**
 * Precaching all the things!
 * - All data is specified in _config.yml
 * - This empty array will be filled out via jekyll
 */
workbox.precaching.precacheAndRoute([]);

/**
 * Routing for different filetypes & endpoints
 * - Force all goodreads, google analytics calls to network only
 * - Force all p.gif calls (Adobe's check to see if you can use the font) to
 *   network only. If this is cached, the cache grows and grows
 * - Cache the actual font files (use.typekit.net/af) for 120 days
 * - Force all fonts stylesheets/js to network only for opaque caching probs
 * - Cache js, css, and json for six hours
 * - Cache up to 60 images for 30 days
 */

const vendorRouteConfiguration = function(hours) {
  return {
    cacheName: vendorCacheName,
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200]
      }),
      new workbox.expiration.Plugin({
        maxAgeSeconds: 60 * 60 * hours
      })
    ]
  };
};

workbox.routing.registerRoute(
  /.*images\.gr-assets\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /.*goodreads\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /.*google-analytics\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/*google*\.com/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net\/p\.gif/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net\/af/,
  workbox.strategies.cacheFirst(vendorRouteConfiguration(24 * 120))
);
workbox.routing.registerRoute(
  /^https?:\/\/.*.typekit\.net/,
  workbox.strategies.networkOnly()
);
workbox.routing.registerRoute(
  /.*\.(?:js|css|json)$/,
  workbox.strategies.cacheFirst({
    cacheName: staticCacheName,
    plugins: [
      new workbox.expiration.Plugin({
        maxAgeSeconds: 60 * 60 * 6
      })
    ]
  })
);
workbox.routing.registerRoute(
  /\.(?:png|gif|jpg|jpeg|svg|webp|ico)$/,
  workbox.strategies.cacheFirst({
    cacheName: imageCacheName,
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 60,
        maxAgeSeconds: 60 * 60 * 24 * 30
      })
    ]
  })
);

/**
 * Returns a generic error page from cache, replacing the error content with
 * status and the actual page URL.
 */
const errorPageForURL = async(pageURL) => {
  const cachedResponse = await caches.match(genericErrorPage);

  return responseWithReplacedErrorContent(cachedResponse, pageURL, 500);
};

/**
 * Returns the offline page from cache, replacing the error content with a 503
 * status and the actual page URL.
 */
const offlinePageForURL = async(pageURL) => {
  const cachedResponse = await caches.match(offlinePage);

  return responseWithReplacedErrorContent(cachedResponse, pageURL, 503);
};

/**
 * Takes an existing response and replaces some error content. Specifically:
 * - Replaces "<error>"" or "</error>" with the actual status code
 * - Replaces "? page" with the actual pageURL
 *
 * Note that the status code falls back to the response's status if not defined.
 */
function responseWithReplacedErrorContent(existingResponse, pageURL, status) {
  if (!status) {
    status = existingResponse.status;
  }

  const constructedResponse = {
    status: status,
    statusText: existingResponse.statusText,
    headers: existingResponse.headers
  };

  // Replace the requisite strings in the page with the real URL and status.
  return existingResponse.text().then(function(body) {
    const left = "<";
    const right = status + ">";

    var newContent = body.replace(/\? page/, pageURL);
    newContent = newContent.replace(/<error>/, left + right);
    newContent = newContent.replace(/<\/error>/, left + "/" + right);

    return new Response(newContent, constructedResponse);
  });
}

/**
 * Handles a standard network response. There are two main components here:
 * - If the response 404'd, we need to replace the placeholder error content
 * - Otherwise, check if we're requesting a document
 * - If a document, return the response or fall back to the offline page
 * - Otherwise, just return the response
 */
function handleResponse(response, pageURL, destination) {
  // If the response 404'd, we should replace the page URL and return it
  if (response && response.status === 404) {
    return responseWithReplacedErrorContent(response, pageURL);
  }

  // Check if we should try returning offline page if response fails
  switch (destination) {
    case "document":
      return response || offlinePageForURL(pageURL);
    default:
      return response;
  }
}

/**
 * Fetches a page, doing all the work to handle edge cases. Specifically:
 * - For not-GET methods, uses networkOnly to not cache them
 * - If there's a preload response, just use that
 * - Everything else is run through handleResponse with networkFirst strategy
 */
const fetchPage = async(context) => {
  const nonGetStrategy = workbox.strategies.networkOnly();

  // If it's non GET request, use networkOnly
  if (context.event.request.method !== "GET") {
    return nonGetStrategy.handle(context);
  }

  // Use the preloaded response, if it's there
  const preloadResponse = await context.preloadResponse;

  if (preloadResponse) {
    return preloadResponse;
  }

  // Otherwise, fetch and handle the response
  const pageURL = context.event.request.url;
  const destination = context.event.request.destination;
  const defaultStrategy = workbox.strategies.networkFirst();
  const response = await defaultStrategy.handle(context);

  return handleResponse(response, pageURL, destination);
};

/**
 * If it's a document, it fetches the fallback error page if possible, or
 * returns a generic response error if not.
 */
function fetchErrorPage(context) {
  const pageURL = context.event.request.url;
  const destination = context.event.request.destination;

  switch (destination) {
    case "document":
      return errorPageForURL(pageURL);
    default:
      return Response.error();
  }
}

workbox.routing.setDefaultHandler(fetchPage);
workbox.routing.setCatchHandler(fetchErrorPage);
<|endoftext|>


<commit_before>/*
   Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.

   This file is licensed under the Apache License, Version 2.0 (the "License").
   You may not use this file except in compliance with the License. A copy of
   the License is located at

    http://aws.amazon.com/apache2.0/

   This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied. See the License for the
   specific language governing permissions and limitations under the License.

   ABOUT THIS SAMPLE: This sample is intended for Node.js execution as part of the Developer Guide topic at
   https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/cloudwatch-examples-creating-alarms.html
*/

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'REGION'});

// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});

var params = {
  AlarmNames: ['Web_Server_CPU_Utilization']
};

cw.deleteAlarms(params, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data);
  }
});
<commit_msg>Update cw_deletealarms.js<commit_after>// Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Licensed under the Apache-2.0 License on an "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND.

// ABOUT THIS NODE.JS SAMPLE: This sample is part of the SDK for JavaScript Developer Guide topic at
// https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/cloudwatch-examples-creating-alarms.html
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'REGION'});

// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});

var params = {
  AlarmNames: ['Web_Server_CPU_Utilization']
};

cw.deleteAlarms(params, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data);
  }
});
<|endoftext|>


<commit_before>import Artists      from "./ui/artists.js";
import Comments     from "./ui/comments.js";
import ForumPosts   from "./ui/forum_posts.js";;
import ModeMenu     from "./ui/mode_menu.js";
import Pools        from "./ui/pools.js";
import Posts        from "./ui/posts.js";
import PostVersions from "./ui/post_versions.js";
import Users        from "./ui/users.js";
import WikiPages    from "./ui/wiki_pages.js";

import Tag from "./tag.js";

export default class UI {
  static initialize() {
    UI.initialize_moment();
    UI.initialize_patches();

    EX.config.showHeaderBar && UI.initialize_header();
    EX.config.showThumbnailPreviews && UI.initialize_post_thumbnail_previews();
    EX.config.showPostLinkPreviews && UI.initialize_post_link_previews();
    EX.config.styleUsernames && UI.initialize_user_links();
    EX.config.styleWikiLinks && UI.initialize_wiki_links();
    EX.config.useRelativeTimestamps && UI.initialize_relative_times();
    UI.initialize_hotkeys();
  }

  // Prevent middle-click from adding tag when clicking on related tags (open a new tab instead).
  static initialize_patches() {
    const old_toggle_tag = Danbooru.RelatedTag.toggle_tag;
    Danbooru.RelatedTag.toggle_tag = function (e) {
      if (e.which === 1) {
        return old_toggle_tag(e);
      }
    };
  }

  /*
   * Add sticky header.
   */
  static initialize_header() {
    let $sticky = $(`

Danbooru

        <form id="search-box" action="/posts" accept-charset="UTF-8" method="get">
          <input name="utf8" type="hidden" value="✓">
          <input type="text" name="tags" id="tags" size="20" class="ui-autocomplete-input" autocomplete="off">
          <input type="submit" value="Go">
        </form>

          <form action="/">
            <select name="mode">
              <option value="view">View</option>
              <option value="edit">Edit</option>
              <option value="tag-script">Tag script</option>
              <option value="add-fav">Favorite</option>
              <option value="remove-fav">Unfavorite</option>
              <option value="rating-s">Rate safe</option>
              <option value="rating-q">Rate questionable</option>
              <option value="rating-e">Rate explicit</option>
              <option value="vote-up">Vote up</option>
              <option value="vote-down">Vote down</option>
              <option value="lock-rating">Lock rating</option>
              <option value="lock-note">Lock notes</option>
              <option value="approve">Approve</option>
            </select>
          </form>
          <input id="tag-script-field" placeholder="Enter tag script" style="display: none; margin-top: 0.5em;">
          <button type="button">Apply</button>

    `).insertBefore("#top");

    // Initalize sticky header search box.
    $("#sticky-header #tags").val($("#sidebar #tags").val());

    /* Q: Focus search box. */
    /* XXX: Doesn't override site keybinding. */
    /*
    $(document).keydown("keydown", "q", e => {
        let $input = $("#tags, #search_name, #search_name_matches, #query").first();
        console.log($input);

        // Add a space to end if box is non-empty and doesn't already have trailing space.
        $input.val().length && $input.val((i, v) => v.replace(/\s*$/, ' '));
        $input.first().trigger("focus").selectEnd();

        e.preventDefault();
    });
    */

    // Shift+Q: Focus and select all in search box.
    $(document).keydown('shift+q', e => {
      e.preventDefault();

      let $input = $("#tags, #search_name, #search_name_matches, #query").first();

      // Add a space to end if  box is non-empty and doesn't already have trailing space.
      $input.val().length && $input.val((i, v) => v.replace(/\s*$/, ' '));
      $input.focus().selectRange(0, $input.val().length);
    });
  }

  // Use relative times everywhere.
  static initialize_relative_times() {
    const ABS_DATE = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/;
    const abs_dates = $('time').filter((i, e) => $(e).text().match(ABS_DATE));

    abs_dates.each((i, e) => {
      const time_ago = moment($(e).attr('datetime')).fromNow();
      $(e).text(time_ago);
    });
  }

  static initialize_moment() {
    moment.locale("en-short", {
      relativeTime : {
          future: "in %s",
          past:   "%s",
          s:  "s",
          m:  "1m",
          mm: "%dm",
          h:  "1h",
          hh: "%dh",
          d:  "1d",
          dd: "%dd",
          M:  "1m",
          MM: "%dm",
          y:  "1y",
          yy: "%dy"
      }
    });

    moment.locale("en");
    moment.defaultFormat = "MMMM Do YYYY, h:mm a";
  }

  // Show post previews when hovering over post #1234 links.
  static initialize_post_link_previews() {
    $('a[href^="/posts/"]')
      .filter((i, e) => /post #\d+/.test($(e).text()))
      .addClass('ex-thumbnail-tooltip-link');

    UI.install_tooltips();
  }

  // Show post previews when hovering over thumbnails.
  static initialize_post_thumbnail_previews() {
    // The thumbnail container is .post-preview on every page but comments and
    // the mod queue. Handle those specially.
    if ($("#c-comments").length) {
      $("#c-comments .post-preview .preview img").addClass('ex-thumbnail-tooltip-link');
    } else if ($("#c-post-moderator-queues").length) {
      $("#c-post-moderator-queues .mod-queue-preview aside img").addClass('ex-thumbnail-tooltip-link');
    } else {
      $(".post-preview img").addClass('ex-thumbnail-tooltip-link');
    }

    UI.install_tooltips();
  }

  static install_tooltips(items) {
    const max_size = 450;

    $(".ex-thumbnail-tooltip-link").tooltip({
      items: "*",
      content: `<div style="width: ${max_size}px; height: ${max_size}px"></div>`,
      show: { delay: 350 },
      position: {
        my: "left+10 top",
        at: "right top",
      },
      open: (e, ui) => {
        try {
          let $e = $(e.toElement);
          let $link = $e;

          if ($e.prop("nodeName") === "IMG") {
            $link = $e.closest("a");
          }

          const id = $link.attr('href').match(/\/posts\/(\d+)/)[1];

          $.getJSON(`/posts/${id}.json`).then(post =>
            $(ui.tooltip).html(Posts.preview(post, post.large_file_url, "ex-thumbnail-tooltip"))
          );
        } catch (e) {
          console.log(e);
        }
      }
    });
  }

  // Add data attributes to usernames so that banned users and approvers can be styled.
  static initialize_user_links() {
    const user_ids =
      _($('a[href^="/users"]'))
      .map(e => $(e).attr('href').replace("/users/", ""))
      .map(Number)
      .sortBy()
      .sortedUniq()
      .join(',');

    // XXX should do lookup in batches.
    $.getJSON(`/users.json?limit=1000&search[id]=${user_ids}`).then(users => {
      for (const user of users) {
        let $user = $(`a[href^="/users/${user.id}"]`);

        // $user.addClass(`user-${user.level_string.toLowerCase()}`);

        _(user).forOwn((value, key) =>
          $user.attr(`data-${_(key).kebabCase()}`, value)
        );
      }
    });
  }

  // Color code tags linking to wiki pages. Also add a tooltip showing the tag
  // creation date and post count.
  static initialize_wiki_links() {
    const meta_wikis = /^(about:|disclaimer:|help:|howto:|list_of|pool_group:|tag_group:|template:)/i;

    const $wiki_links =
      $(`a[href^="/wiki_pages/show_or_new?title="]`)
      .filter((i, e) => $(e).text() != "?");

    const tag_names =
      _($wiki_links.toArray())
      .map(e =>
        $(e).attr('href').match(/^\/wiki_pages\/show_or_new\?title=(.*)/)[1]
      )
      .map(decodeURIComponent)
      .reject(tag => tag.match(meta_wikis))
      .value();

    // Fetch tag data for each batch of tags, then categorize them and add tooltips.
    Tag.search(tag_names).then(tags => {
      $wiki_links.each((i, e) => {
        const $wiki_link = $(e);
        const name = decodeURIComponent($(e).attr('href').match(/^\/wiki_pages\/show_or_new\?title=(.*)/)[1]);
        const tag = tags[name];

        if (tag.match(meta_wikis)) {
          return;
        } else if (tag === undefined) {
          $wiki_link.addClass('tag-dne');
          return;
        }

        const tag_created_at = moment(tag.created_at).format('MMMM Do YYYY, h:mm:ss a');

        const tag_title =
          `${Tag.Categories[tag.category]} tag #${tag.id} - ${tag.post_count} posts - created on ${tag_created_at}`;

        _(tag).forOwn((value, key) =>
          $wiki_link.attr(`data-tag-${_(key).kebabCase()}`, value)
        );

        $wiki_link.addClass(`tag-type-${tag.category}`).attr('title', tag_title);

        if (tag.post_count === 0) {
          $wiki_link.addClass("tag-post-count-empty");
        } else if (tag.post_count < 100) {
          $wiki_link.addClass("tag-post-count-small");
        } else if (tag.post_count < 1000) {
          $wiki_link.addClass("tag-post-count-medium");
        } else if (tag.post_count < 10000) {
          $wiki_link.addClass("tag-post-count-large");
        } else if (tag.post_count < 100000) {
          $wiki_link.addClass("tag-post-count-huge");
        } else {
          $wiki_link.addClass("tag-post-count-gigantic");
        }
      });
    });
  }

  /*
   * Global keybindings.
   * - Escape: Close notice popups.
   * - W: Smooth scroll up.
   * - S: Smooth scroll down.
   * - Shift+Q: Focus top search bar.
   */
  static initialize_hotkeys() {
    // Escape: Close notice popups.
    $(document).keydown('esc', e => $('#close-notice-link').click());

    // Escape: Unfocus text entry field.
    $('#tag-script-field').attr('type', 'text');
    $('input[type=text],textarea').keydown('esc', e => $(e.currentTarget).blur());

    UI.initialize_scroll_hotkeys();

    if ($(".paginator").length) {
      UI.initialize_paginator_hotkeys();
    }
  }

  static initialize_scroll_hotkeys() {
    let scroll = (direction, duration, distance) =>
      _.throttle(() => {
        const top = $(window).scrollTop() + direction * $(window).height() * distance;
        $('html, body').animate({scrollTop: top}, duration, "linear");
      }, duration);
    /*
    Danbooru.Shortcuts.nav_scroll_down =
        () => Danbooru.scroll_to($(window).scrollTop() + $(window).height() * 0.15);
    Danbooru.Shortcuts.nav_scroll_up =
        () => Danbooru.scroll_to($(window).scrollTop() - $(window).height() * 0.15);
    */

    // Enable smooth scrolling with W/S keys.
    Danbooru.Shortcuts.nav_scroll_down = scroll(+1, 50, 0.06);
    Danbooru.Shortcuts.nav_scroll_up   = scroll(-1, 50, 0.06);
  }

  /*
   * Shift+1..9: Jump to page N.
   * Shift+0: Jump to last page.
   */
  static initialize_paginator_hotkeys() {
    // Add paginator above results.
    // $('.paginator').clone().insertBefore('#post-sections');

    /* Shift+1..9: Jump to page N. */
    [1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n =>
      $(document).keydown(`shift+${n}`, e => {
        UI.gotoPage(n);
        e.preventDefault();
      })
    );

    // Shift+0: Switch to last page if there is one.
    $(document).keydown(`shift+0`, e => {
      e.preventDefault();

      // a:not(a[rel]) - exclude the Previous/Next links seen in the paginator on /favorites et al.
      const last_page = $('div.paginator li:nth-last-child(2) a:not(a[rel])').first().text();

      if (last_page) {
        UI.gotoPage(last_page);
      }
    });
  }

  static linkTo(name, path = "/", params = {}, ...classes) {
    const query = $.param(params);
    const href = (query === "")
               ? path
               : path + "?" + query;

    return `<a class="${_.escape(classes.join(" "))}" href="${href}">${_.escape(name)}</a>`;
  }

  static query(param) {
    return new URL(window.location).searchParams.get(param);
  }

  static openEditPage(controller) {
    // FIXME: Get the ID from the 'Show' link. This is brittle.
    const $show_link =
      $('#nav > menu:nth-child(2) a')
      .filter((i, e) => $(e).text().match(/^Show$/));

    const id = $show_link.attr('href').match(new RegExp(`/${controller}/(\\d+)$`))[1];

    window.location.href = `/${controller}/${id}/edit`;
  }

  // Go to page N.
  static gotoPage(n) {
    if (location.search.match(/page=(\d+)/)) {
      location.search = location.search.replace(/page=(\d+)/, `page=${n}`);
    } else {
      location.search += `&page=${n}`;
    }
  }
}

UI.Artists = Artists;
UI.Comments = Comments;
UI.ForumPosts = ForumPosts;
UI.ModeMenu = ModeMenu;
UI.Pools = Pools;
UI.Posts = Posts;
UI.PostVersions = PostVersions;
UI.Users = Users;
UI.WikiPages = WikiPages;
<commit_msg>[fix] initialize_wiki_links: Don't repeat tag parsing code.<commit_after>import Artists      from "./ui/artists.js";
import Comments     from "./ui/comments.js";
import ForumPosts   from "./ui/forum_posts.js";;
import ModeMenu     from "./ui/mode_menu.js";
import Pools        from "./ui/pools.js";
import Posts        from "./ui/posts.js";
import PostVersions from "./ui/post_versions.js";
import Users        from "./ui/users.js";
import WikiPages    from "./ui/wiki_pages.js";

import Tag from "./tag.js";

export default class UI {
  static initialize() {
    UI.initialize_moment();
    UI.initialize_patches();

    EX.config.showHeaderBar && UI.initialize_header();
    EX.config.showThumbnailPreviews && UI.initialize_post_thumbnail_previews();
    EX.config.showPostLinkPreviews && UI.initialize_post_link_previews();
    EX.config.styleUsernames && UI.initialize_user_links();
    EX.config.styleWikiLinks && UI.initialize_wiki_links();
    EX.config.useRelativeTimestamps && UI.initialize_relative_times();
    UI.initialize_hotkeys();
  }

  // Prevent middle-click from adding tag when clicking on related tags (open a new tab instead).
  static initialize_patches() {
    const old_toggle_tag = Danbooru.RelatedTag.toggle_tag;
    Danbooru.RelatedTag.toggle_tag = function (e) {
      if (e.which === 1) {
        return old_toggle_tag(e);
      }
    };
  }

  /*
   * Add sticky header.
   */
  static initialize_header() {
    let $sticky = $(`

Danbooru

        <form id="search-box" action="/posts" accept-charset="UTF-8" method="get">
          <input name="utf8" type="hidden" value="✓">
          <input type="text" name="tags" id="tags" size="20" class="ui-autocomplete-input" autocomplete="off">
          <input type="submit" value="Go">
        </form>

          <form action="/">
            <select name="mode">
              <option value="view">View</option>
              <option value="edit">Edit</option>
              <option value="tag-script">Tag script</option>
              <option value="add-fav">Favorite</option>
              <option value="remove-fav">Unfavorite</option>
              <option value="rating-s">Rate safe</option>
              <option value="rating-q">Rate questionable</option>
              <option value="rating-e">Rate explicit</option>
              <option value="vote-up">Vote up</option>
              <option value="vote-down">Vote down</option>
              <option value="lock-rating">Lock rating</option>
              <option value="lock-note">Lock notes</option>
              <option value="approve">Approve</option>
            </select>
          </form>
          <input id="tag-script-field" placeholder="Enter tag script" style="display: none; margin-top: 0.5em;">
          <button type="button">Apply</button>

    `).insertBefore("#top");

    // Initalize sticky header search box.
    $("#sticky-header #tags").val($("#sidebar #tags").val());

    /* Q: Focus search box. */
    /* XXX: Doesn't override site keybinding. */
    /*
    $(document).keydown("keydown", "q", e => {
        let $input = $("#tags, #search_name, #search_name_matches, #query").first();
        console.log($input);

        // Add a space to end if box is non-empty and doesn't already have trailing space.
        $input.val().length && $input.val((i, v) => v.replace(/\s*$/, ' '));
        $input.first().trigger("focus").selectEnd();

        e.preventDefault();
    });
    */

    // Shift+Q: Focus and select all in search box.
    $(document).keydown('shift+q', e => {
      e.preventDefault();

      let $input = $("#tags, #search_name, #search_name_matches, #query").first();

      // Add a space to end if  box is non-empty and doesn't already have trailing space.
      $input.val().length && $input.val((i, v) => v.replace(/\s*$/, ' '));
      $input.focus().selectRange(0, $input.val().length);
    });
  }

  // Use relative times everywhere.
  static initialize_relative_times() {
    const ABS_DATE = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/;
    const abs_dates = $('time').filter((i, e) => $(e).text().match(ABS_DATE));

    abs_dates.each((i, e) => {
      const time_ago = moment($(e).attr('datetime')).fromNow();
      $(e).text(time_ago);
    });
  }

  static initialize_moment() {
    moment.locale("en-short", {
      relativeTime : {
          future: "in %s",
          past:   "%s",
          s:  "s",
          m:  "1m",
          mm: "%dm",
          h:  "1h",
          hh: "%dh",
          d:  "1d",
          dd: "%dd",
          M:  "1m",
          MM: "%dm",
          y:  "1y",
          yy: "%dy"
      }
    });

    moment.locale("en");
    moment.defaultFormat = "MMMM Do YYYY, h:mm a";
  }

  // Show post previews when hovering over post #1234 links.
  static initialize_post_link_previews() {
    $('a[href^="/posts/"]')
      .filter((i, e) => /post #\d+/.test($(e).text()))
      .addClass('ex-thumbnail-tooltip-link');

    UI.install_tooltips();
  }

  // Show post previews when hovering over thumbnails.
  static initialize_post_thumbnail_previews() {
    // The thumbnail container is .post-preview on every page but comments and
    // the mod queue. Handle those specially.
    if ($("#c-comments").length) {
      $("#c-comments .post-preview .preview img").addClass('ex-thumbnail-tooltip-link');
    } else if ($("#c-post-moderator-queues").length) {
      $("#c-post-moderator-queues .mod-queue-preview aside img").addClass('ex-thumbnail-tooltip-link');
    } else {
      $(".post-preview img").addClass('ex-thumbnail-tooltip-link');
    }

    UI.install_tooltips();
  }

  static install_tooltips(items) {
    const max_size = 450;

    $(".ex-thumbnail-tooltip-link").tooltip({
      items: "*",
      content: `<div style="width: ${max_size}px; height: ${max_size}px"></div>`,
      show: { delay: 350 },
      position: {
        my: "left+10 top",
        at: "right top",
      },
      open: (e, ui) => {
        try {
          let $e = $(e.toElement);
          let $link = $e;

          if ($e.prop("nodeName") === "IMG") {
            $link = $e.closest("a");
          }

          const id = $link.attr('href').match(/\/posts\/(\d+)/)[1];

          $.getJSON(`/posts/${id}.json`).then(post =>
            $(ui.tooltip).html(Posts.preview(post, post.large_file_url, "ex-thumbnail-tooltip"))
          );
        } catch (e) {
          console.log(e);
        }
      }
    });
  }

  // Add data attributes to usernames so that banned users and approvers can be styled.
  static initialize_user_links() {
    const user_ids =
      _($('a[href^="/users"]'))
      .map(e => $(e).attr('href').replace("/users/", ""))
      .map(Number)
      .sortBy()
      .sortedUniq()
      .join(',');

    // XXX should do lookup in batches.
    $.getJSON(`/users.json?limit=1000&search[id]=${user_ids}`).then(users => {
      for (const user of users) {
        let $user = $(`a[href^="/users/${user.id}"]`);

        // $user.addClass(`user-${user.level_string.toLowerCase()}`);

        _(user).forOwn((value, key) =>
          $user.attr(`data-${_(key).kebabCase()}`, value)
        );
      }
    });
  }

  // Color code tags linking to wiki pages. Also add a tooltip showing the tag
  // creation date and post count.
  static initialize_wiki_links() {
    function parse_tag_name(wiki_link) {
      return decodeURIComponent($(wiki_link).attr('href').match(/^\/wiki_pages\/show_or_new\?title=(.*)/)[1]);
    }

    const meta_wikis = /^(about:|disclaimer:|help:|howto:|list_of|pool_group:|tag_group:|template:)/i;

    const $wiki_links =
      $(`a[href^="/wiki_pages/show_or_new?title="]`)
      .filter((i, e) => $(e).text() != "?");

    const tag_names =
      _($wiki_links.toArray())
      .map(parse_tag_name)
      .reject(tag => tag.match(meta_wikis))
      .value();

    // Fetch tag data for each batch of tags, then categorize them and add tooltips.
    Tag.search(tag_names).then(tags => {
      $wiki_links.each((i, e) => {
        const $wiki_link = $(e);
        const name = parse_tag_name($wiki_link);
        const tag = tags[name];

        if (name.match(meta_wikis)) {
          return;
        } else if (tag === undefined) {
          $wiki_link.addClass('tag-dne');
          return;
        }

        const tag_created_at = moment(tag.created_at).format('MMMM Do YYYY, h:mm:ss a');

        const tag_title =
          `${Tag.Categories[tag.category]} tag #${tag.id} - ${tag.post_count} posts - created on ${tag_created_at}`;

        _(tag).forOwn((value, key) =>
          $wiki_link.attr(`data-tag-${_(key).kebabCase()}`, value)
        );

        $wiki_link.addClass(`tag-type-${tag.category}`).attr('title', tag_title);

        if (tag.post_count === 0) {
          $wiki_link.addClass("tag-post-count-empty");
        } else if (tag.post_count < 100) {
          $wiki_link.addClass("tag-post-count-small");
        } else if (tag.post_count < 1000) {
          $wiki_link.addClass("tag-post-count-medium");
        } else if (tag.post_count < 10000) {
          $wiki_link.addClass("tag-post-count-large");
        } else if (tag.post_count < 100000) {
          $wiki_link.addClass("tag-post-count-huge");
        } else {
          $wiki_link.addClass("tag-post-count-gigantic");
        }
      });
    });
  }

  /*
   * Global keybindings.
   * - Escape: Close notice popups.
   * - W: Smooth scroll up.
   * - S: Smooth scroll down.
   * - Shift+Q: Focus top search bar.
   */
  static initialize_hotkeys() {
    // Escape: Close notice popups.
    $(document).keydown('esc', e => $('#close-notice-link').click());

    // Escape: Unfocus text entry field.
    $('#tag-script-field').attr('type', 'text');
    $('input[type=text],textarea').keydown('esc', e => $(e.currentTarget).blur());

    UI.initialize_scroll_hotkeys();

    if ($(".paginator").length) {
      UI.initialize_paginator_hotkeys();
    }
  }

  static initialize_scroll_hotkeys() {
    let scroll = (direction, duration, distance) =>
      _.throttle(() => {
        const top = $(window).scrollTop() + direction * $(window).height() * distance;
        $('html, body').animate({scrollTop: top}, duration, "linear");
      }, duration);
    /*
    Danbooru.Shortcuts.nav_scroll_down =
        () => Danbooru.scroll_to($(window).scrollTop() + $(window).height() * 0.15);
    Danbooru.Shortcuts.nav_scroll_up =
        () => Danbooru.scroll_to($(window).scrollTop() - $(window).height() * 0.15);
    */

    // Enable smooth scrolling with W/S keys.
    Danbooru.Shortcuts.nav_scroll_down = scroll(+1, 50, 0.06);
    Danbooru.Shortcuts.nav_scroll_up   = scroll(-1, 50, 0.06);
  }

  /*
   * Shift+1..9: Jump to page N.
   * Shift+0: Jump to last page.
   */
  static initialize_paginator_hotkeys() {
    // Add paginator above results.
    // $('.paginator').clone().insertBefore('#post-sections');

    /* Shift+1..9: Jump to page N. */
    [1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n =>
      $(document).keydown(`shift+${n}`, e => {
        UI.gotoPage(n);
        e.preventDefault();
      })
    );

    // Shift+0: Switch to last page if there is one.
    $(document).keydown(`shift+0`, e => {
      e.preventDefault();

      // a:not(a[rel]) - exclude the Previous/Next links seen in the paginator on /favorites et al.
      const last_page = $('div.paginator li:nth-last-child(2) a:not(a[rel])').first().text();

      if (last_page) {
        UI.gotoPage(last_page);
      }
    });
  }

  static linkTo(name, path = "/", params = {}, ...classes) {
    const query = $.param(params);
    const href = (query === "")
               ? path
               : path + "?" + query;

    return `<a class="${_.escape(classes.join(" "))}" href="${href}">${_.escape(name)}</a>`;
  }

  static query(param) {
    return new URL(window.location).searchParams.get(param);
  }

  static openEditPage(controller) {
    // FIXME: Get the ID from the 'Show' link. This is brittle.
    const $show_link =
      $('#nav > menu:nth-child(2) a')
      .filter((i, e) => $(e).text().match(/^Show$/));

    const id = $show_link.attr('href').match(new RegExp(`/${controller}/(\\d+)$`))[1];

    window.location.href = `/${controller}/${id}/edit`;
  }

  // Go to page N.
  static gotoPage(n) {
    if (location.search.match(/page=(\d+)/)) {
      location.search = location.search.replace(/page=(\d+)/, `page=${n}`);
    } else {
      location.search += `&page=${n}`;
    }
  }
}

UI.Artists = Artists;
UI.Comments = Comments;
UI.ForumPosts = ForumPosts;
UI.ModeMenu = ModeMenu;
UI.Pools = Pools;
UI.Posts = Posts;
UI.PostVersions = PostVersions;
UI.Users = Users;
UI.WikiPages = WikiPages;
<|endoftext|>


<commit_before>/* jshint strict: false, sub: true */
/* global */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
// / Copyright 2014 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// / @author Max Neunhoeffer
// //////////////////////////////////////////////////////////////////////////////

const functionsDocumentation = {
  'replication_ongoing': 'replication ongoing tests',
  'replication_static': 'replication static tests',
  'replication_sync': 'replication sync tests',
  'shell_replication': 'shell replication tests'
};
const optionsDocumentation = [
];

const _ = require('lodash');
const pu = require('@arangodb/process-utils');
const tu = require('@arangodb/test-utils');

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: shell_replication
// //////////////////////////////////////////////////////////////////////////////

function shellReplication (options) {
  let testCases = tu.scanTestPath('js/common/tests/replication');

  var opts = {
    'replication': true
  };
  _.defaults(opts, options);

  return tu.performTests(opts, testCases, 'shell_replication', tu.runThere);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_ongoing
// //////////////////////////////////////////////////////////////////////////////

function replicationOngoing (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-ongoing';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_static
// //////////////////////////////////////////////////////////////////////////////

function replicationStatic (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-static';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.grantDatabase("replicator-user", "_system");
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);
      return {};
    }
  };

  return tu.performTests(
    localOptions,
    testCases,
    'master_static',
    tu.runInArangosh,
    {
      'server.authentication': 'true'
    },
    startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_sync
// //////////////////////////////////////////////////////////////////////////////

function replicationSync (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-sync';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStopfixx: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

function setup (testFns, defaultFns, opts, fnDocs, optionsDoc) {
  testFns['shell_replication'] = shellReplication;
  testFns['replication_ongoing'] = replicationOngoing;
  testFns['replication_static'] = replicationStatic;
  testFns['replication_sync'] = replicationSync;
  for (var attrname in functionsDocumentation) { fnDocs[attrname] = functionsDocumentation[attrname]; }
  for (var i = 0; i < optionsDocumentation.length; i++) { optionsDoc.push(optionsDocumentation[i]); }
}

exports.setup = setup;
<commit_msg>Revert "Break tests again"<commit_after>/* jshint strict: false, sub: true */
/* global */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
// / Copyright 2014 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// / @author Max Neunhoeffer
// //////////////////////////////////////////////////////////////////////////////

const functionsDocumentation = {
  'replication_ongoing': 'replication ongoing tests',
  'replication_static': 'replication static tests',
  'replication_sync': 'replication sync tests',
  'shell_replication': 'shell replication tests'
};
const optionsDocumentation = [
];

const _ = require('lodash');
const pu = require('@arangodb/process-utils');
const tu = require('@arangodb/test-utils');

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: shell_replication
// //////////////////////////////////////////////////////////////////////////////

function shellReplication (options) {
  let testCases = tu.scanTestPath('js/common/tests/replication');

  var opts = {
    'replication': true
  };
  _.defaults(opts, options);

  return tu.performTests(opts, testCases, 'shell_replication', tu.runThere);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_ongoing
// //////////////////////////////////////////////////////////////////////////////

function replicationOngoing (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-ongoing';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_static
// //////////////////////////////////////////////////////////////////////////////

function replicationStatic (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-static';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.grantDatabase("replicator-user", "_system");
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);
      return {};
    }
  };

  return tu.performTests(
    localOptions,
    testCases,
    'master_static',
    tu.runInArangosh,
    {
      'server.authentication': 'true'
    },
    startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_sync
// //////////////////////////////////////////////////////////////////////////////

function replicationSync (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-sync';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

function setup (testFns, defaultFns, opts, fnDocs, optionsDoc) {
  testFns['shell_replication'] = shellReplication;
  testFns['replication_ongoing'] = replicationOngoing;
  testFns['replication_static'] = replicationStatic;
  testFns['replication_sync'] = replicationSync;
  for (var attrname in functionsDocumentation) { fnDocs[attrname] = functionsDocumentation[attrname]; }
  for (var i = 0; i < optionsDocumentation.length; i++) { optionsDoc.push(optionsDocumentation[i]); }
}

exports.setup = setup;
<|endoftext|>


<commit_before>$(function() {
  init(location.href);

  $(document).pjax(".tabs > a", "#content", {fragment: "#content"});
  $(document).on("pjax:success", function(event, data, status, xhr, options) {
    init(options.url);
  });

  $(window).bind("pageshow orientationchange resize popstate", function() {
    initTabs(location.href);
  });

  $(document).on("click", "button", function(event) {
    $(event.target).blur();
    // firefox os keeps the button in focused state after it is clicked - this will reset it
  });
});

var init = function(url) {
  initTabs(location.href);
  FastClick.attach(document.body);
  initWebViewCookie(location.href);
};

// the tabs should be on the bottom of the screen
var initTabs = function(url) {

  $(".tabs .active").removeClass('active');
  if (url.indexOf("/app.html") > -1) {
    $(".tabs .tab-app").addClass('active');
    initApp();
  } else if (url.indexOf("/stats.html") > -1) {
    $(".tabs .tab-stats").addClass('active');
    initStats();
  } else {
    $(".tabs .tab-add").addClass('active');
    initIndex();
  }

  var footer = $(".button-group");
  var footerHeight = footer.height();
  var footerTop = ($(window).scrollTop() + $(window).height() - footerHeight) + "px";

  if (($("#content").height()+footerHeight) < $(window).height()) {
    footer.css({
      "margin-top": $(window).height() - ($("#content").height() + footerHeight) - 8,
      "margin-bottom": 0
    });
  } else {
    footer.css({
      "margin-top": 10,
      "margin-bottom": 0
    });
  }
};

var initWebViewCookie = function(href) {
  if (href.indexOf("webview=1") > -1) {
    document.cookie = "webview=1";
  }
};

var setCurrentTime = function(text, amount) {
  $(".current-time").text(text);
  $(".current-time").data('amount', amount);
};

var setCurrentDate = function(text, date) {
  $(".current-date").text(text);
  $(".current-date").data('date', date);
};

var updateOvertimeStatementTime = function(total) {
  // put hours and minutes in a div
  if (total >= 0) {
    var hours = parseInt(total / 60);
    var minutes = total % 60;
    if (hours === 1 && minutes === 0) {
      setCurrentTime(hours + ' hour', total);
    } else if (hours > 1 && minutes === 0) {
      setCurrentTime(hours + ' hours', total);
    } else if (minutes >= 0) {
      if (minutes < 10) {
        minutes = '0' + minutes;
      }
      setCurrentTime(hours + ':' + minutes + ' hours', total);
    }
  } else {
    setCurrentTime('', 0);
  }

  toggleStatementDelimiter();
};

var updateOvertimeStatementDate = function(date) {
  // put date representation in a div
  if (!date.isValid()) {
    setCurrentDate('', '');
  } else {

    var now = moment();
    var diff = now.diff(date, 'days');
    var dateString = date.format("YYYY-MM-DD");
    if (diff === 0) {
      // today
      setCurrentDate('Today', dateString);
    } else if (diff === 1) {
      // yesterday
      setCurrentDate('Yesterday', dateString);
    } else {
      setCurrentDate(dateString, dateString);
    }
  }
  toggleStatementDelimiter();
};

var toggleStatementDelimiter = function() {
  if ($(".current-time").text() === '' || $(".current-date").text() === '') {
    $(".current-statement-delim").text('');
  } else {
    $(".current-statement-delim").text(', ');
  }
};

var showMonthlyOvertime = function(event) { // ui
  var userData = getUserData();
  var months = totalOvertimeByMonths(userData);

  var keys = Object.keys(months).sort().reverse();
  var parent = $('body');
  render(templates.monthsListItem, [keys, months], parent);
};

var showDailyOvertime = function(event) { // ui
  var userData = getUserData();
  var yyyymm = event.target.dataset.yyyymm;
  var daily = overtimeByMonth(yyyymm, userData);
  var parent = $('body > div:first');

  $.each(daily, function(i, v) {
    render(templates.dailyOvertime, v, parent);
  });
};

var showThisMonthOvertime = function(event) { // ui
  var userData = getUserData();
  var minutes = totalOvertimeThisMonth(new Date(), userData);

  $('body div:first').text(readableTimeDelta(minutes));
};

var showMessage = function(text) {
  $(".message-text").text(text);
  $(".message").fadeIn('fast').delay(1000).fadeOut('fast');
};

var showError = function(text) {
  $(".error > .message-text").text(text);
  $(".error").fadeIn('fast').delay(5000).fadeOut('fast');
};

var reset = function() {
  setCurrentTime('', 0);
  setCurrentDate('', '');
  toggleStatementDelimiter();
  $('input').val('');

  $(".default-overtime-amount").show('fast');
  $(".custom-overtime-amount").hide('fast');

  $(".default-overtime-date").show('fast');
  $(".custom-overtime-date").hide('fast');
};

var save = function(event) {
  var date = moment($(".current-date").data('date'));
  var minutes = parseInt($(".current-time").data('amount'), 10) || 0;

  if (date.isValid() && minutes >= 0) {
    var userData = getUserData();

    addUserDataEntry(date.format('YYYYMMDD'), minutes, userData);

    $(".reset").trigger('click');

  } else {
    // what?
  }
};

var calculate_time = function(event) {
  var amount = parseInt(event.target.value, 10) || 0;
  var total = 0;
  if (/_minutes$/.test(event.target.name)) {
    total = amount + parseInt($("[name=overtime_hours]").val() || 0, 10) * 60;
  } else if (/_hours$/.test(event.target.name)) {
    total = amount * 60 + parseInt($("[name=overtime_minutes]").val() || 0, 10);
  }

  if (total >= 0) {
    updateOvertimeStatementTime(total);
  } else {
    updateOvertimeStatementTime('');
  }
};

var initIndex = function() {

  // overtime controls
  $(".activate-custom-overtime-amount").on('click', function(event) {
    $(".default-overtime-amount").hide('fast');
    $(".custom-overtime-amount").show('fast');
  });

  $(".activate-custom-overtime-date").on('click', function(event) {
    $(".default-overtime-date").hide('fast');
    $(".custom-overtime-date").show('fast');
  });

  $("[name=overtime_hours], [name=overtime_minutes]").on('change', calculate_time);

  $(".time-amount").on('click', function(event) {
    var amount = (parseInt(event.target.dataset.amount) || 0) * 60;
    updateOvertimeStatementTime(amount);
  });

  $("input[name=overtime_date]").on('change blur', function(event) {
    var date = moment(event.target.value);
    updateOvertimeStatementDate(date);
  });

  // the "when" buttons
  $(".today").on('click', function(event) {
    var date = moment();
    updateOvertimeStatementDate(date);
  });

  $(".yesterday").on('click', function(event) {
    var date = moment().subtract('day', 1);
    updateOvertimeStatementDate(date);
  });

  // form buttons
  $(".save").on('click', save);
  $(".reset").on('click', reset);
};

<commit_msg>Removed unused js<commit_after>$(function() {
  init(location.href);

  $(document).pjax(".tabs > a", "#content", {fragment: "#content"});
  $(document).on("pjax:success", function(event, data, status, xhr, options) {
    init(options.url);
  });

  $(window).bind("pageshow orientationchange resize popstate", function() {
    initTabs(location.href);
  });

  $(document).on("click", "button", function(event) {
    $(event.target).blur();
    // firefox os keeps the button in focused state after it is clicked - this will reset it
  });
});

var init = function(url) {
  initTabs(location.href);
  FastClick.attach(document.body);
  initWebViewCookie(location.href);
};

// the tabs should be on the bottom of the screen
var initTabs = function(url) {

  $(".tabs .active").removeClass('active');
  if (url.indexOf("/app.html") > -1) {
    $(".tabs .tab-app").addClass('active');
    initApp();
  } else if (url.indexOf("/stats.html") > -1) {
    $(".tabs .tab-stats").addClass('active');
    initStats();
  } else {
    $(".tabs .tab-add").addClass('active');
    initIndex();
  }

  var footer = $(".button-group");
  var footerHeight = footer.height();
  var footerTop = ($(window).scrollTop() + $(window).height() - footerHeight) + "px";

  if (($("#content").height()+footerHeight) < $(window).height()) {
    footer.css({
      "margin-top": $(window).height() - ($("#content").height() + footerHeight) - 8,
      "margin-bottom": 0
    });
  } else {
    footer.css({
      "margin-top": 10,
      "margin-bottom": 0
    });
  }
};

var initWebViewCookie = function(href) {
  if (href.indexOf("webview=1") > -1) {
    document.cookie = "webview=1";
  }
};

var setCurrentTime = function(text, amount) {
  $(".current-time").text(text);
  $(".current-time").data('amount', amount);
};

var setCurrentDate = function(text, date) {
  $(".current-date").text(text);
  $(".current-date").data('date', date);
};

var updateOvertimeStatementTime = function(total) {
  // put hours and minutes in a div
  if (total >= 0) {
    var hours = parseInt(total / 60);
    var minutes = total % 60;
    if (hours === 1 && minutes === 0) {
      setCurrentTime(hours + ' hour', total);
    } else if (hours > 1 && minutes === 0) {
      setCurrentTime(hours + ' hours', total);
    } else if (minutes >= 0) {
      if (minutes < 10) {
        minutes = '0' + minutes;
      }
      setCurrentTime(hours + ':' + minutes + ' hours', total);
    }
  } else {
    setCurrentTime('', 0);
  }

  toggleStatementDelimiter();
};

var updateOvertimeStatementDate = function(date) {
  // put date representation in a div
  if (!date.isValid()) {
    setCurrentDate('', '');
  } else {

    var now = moment();
    var diff = now.diff(date, 'days');
    var dateString = date.format("YYYY-MM-DD");
    if (diff === 0) {
      // today
      setCurrentDate('Today', dateString);
    } else if (diff === 1) {
      // yesterday
      setCurrentDate('Yesterday', dateString);
    } else {
      setCurrentDate(dateString, dateString);
    }
  }
  toggleStatementDelimiter();
};

var toggleStatementDelimiter = function() {
  if ($(".current-time").text() === '' || $(".current-date").text() === '') {
    $(".current-statement-delim").text('');
  } else {
    $(".current-statement-delim").text(', ');
  }
};

var showMessage = function(text) {
  $(".message-text").text(text);
  $(".message").fadeIn('fast').delay(1000).fadeOut('fast');
};

var showError = function(text) {
  $(".error > .message-text").text(text);
  $(".error").fadeIn('fast').delay(5000).fadeOut('fast');
};

var reset = function() {
  setCurrentTime('', 0);
  setCurrentDate('', '');
  toggleStatementDelimiter();
  $('input').val('');

  $(".default-overtime-amount").show('fast');
  $(".custom-overtime-amount").hide('fast');

  $(".default-overtime-date").show('fast');
  $(".custom-overtime-date").hide('fast');
};

var save = function(event) {
  var date = moment($(".current-date").data('date'));
  var minutes = parseInt($(".current-time").data('amount'), 10) || 0;

  if (date.isValid() && minutes >= 0) {
    var userData = getUserData();

    addUserDataEntry(date.format('YYYYMMDD'), minutes, userData);

    $(".reset").trigger('click');

  } else {
    // what?
  }
};

var calculate_time = function(event) {
  var amount = parseInt(event.target.value, 10) || 0;
  var total = 0;
  if (/_minutes$/.test(event.target.name)) {
    total = amount + parseInt($("[name=overtime_hours]").val() || 0, 10) * 60;
  } else if (/_hours$/.test(event.target.name)) {
    total = amount * 60 + parseInt($("[name=overtime_minutes]").val() || 0, 10);
  }

  if (total >= 0) {
    updateOvertimeStatementTime(total);
  } else {
    updateOvertimeStatementTime('');
  }
};

var initIndex = function() {

  // overtime controls
  $(".activate-custom-overtime-amount").on('click', function(event) {
    $(".default-overtime-amount").hide('fast');
    $(".custom-overtime-amount").show('fast');
  });

  $(".activate-custom-overtime-date").on('click', function(event) {
    $(".default-overtime-date").hide('fast');
    $(".custom-overtime-date").show('fast');
  });

  $("[name=overtime_hours], [name=overtime_minutes]").on('change', calculate_time);

  $(".time-amount").on('click', function(event) {
    var amount = (parseInt(event.target.dataset.amount) || 0) * 60;
    updateOvertimeStatementTime(amount);
  });

  $("input[name=overtime_date]").on('change blur', function(event) {
    var date = moment(event.target.value);
    updateOvertimeStatementDate(date);
  });

  // the "when" buttons
  $(".today").on('click', function(event) {
    var date = moment();
    updateOvertimeStatementDate(date);
  });

  $(".yesterday").on('click', function(event) {
    var date = moment().subtract('day', 1);
    updateOvertimeStatementDate(date);
  });

  // form buttons
  $(".save").on('click', save);
  $(".reset").on('click', reset);
};

<|endoftext|>


<commit_before>import highlight from 'flarum/helpers/highlight';
import ActionButton from 'flarum/components/action-button';

export default class DiscussionsSearchResults {
  constructor() {
    this.results = {};
  }

  search(string) {
    this.results[string] = [];
    return app.store.find('discussions', {q: string, page: {limit: 3}, include: 'relevantPosts,relevantPosts.discussion'}).then(results => {
      this.results[string] = results;
    });
  }

  view(string) {
    return [
      m('li.dropdown-header', 'Discussions'),
      m('li', ActionButton.component({
        icon: 'search',
        label: 'Search all discussions for "'+string+'"',
        href: app.route('index', {q: string}),
        config: m.route
      })),
      (this.results[string] && this.results[string].length) ? this.results[string].map(discussion => {
        var post = discussion.relevantPosts()[0];
        return m('li.discussion-search-result', {'data-index': 'discussions'+discussion.id()},
          m('a', { href: app.route.discussion(discussion, post.number()), config: m.route },
            m('div.title', highlight(discussion.title(), string)),
            m('div.excerpt', highlight(post.contentPlain().substring(0, 100), string))
          )
        );
      }) : ''
    ];
  }
}
<commit_msg>Use truncate for search suggestions<commit_after>import highlight from 'flarum/helpers/highlight';
import truncate from 'flarum/utils/truncate';
import ActionButton from 'flarum/components/action-button';

export default class DiscussionsSearchResults {
  constructor() {
    this.results = {};
  }

  search(string) {
    this.results[string] = [];
    return app.store.find('discussions', {q: string, page: {limit: 3}, include: 'relevantPosts,relevantPosts.discussion'}).then(results => {
      this.results[string] = results;
    });
  }

  view(string) {
    return [
      m('li.dropdown-header', 'Discussions'),
      m('li', ActionButton.component({
        icon: 'search',
        label: 'Search all discussions for "'+string+'"',
        href: app.route('index', {q: string}),
        config: m.route
      })),
      (this.results[string] && this.results[string].length) ? this.results[string].map(discussion => {
        var post = discussion.relevantPosts()[0];
        return m('li.discussion-search-result', {'data-index': 'discussions'+discussion.id()},
          m('a', { href: app.route.discussion(discussion, post.number()), config: m.route },
            m('div.title', highlight(discussion.title(), string)),
            m('div.excerpt', highlight(truncate(post.contentPlain(), 100), string))
          )
        );
      }) : ''
    ];
  }
}
<|endoftext|>