<commit_before>import React from "react";
import gql from "graphql-tag.macro";
import { Row, Col, Container, Card } from "helpers/reactstrap";
import { withApollo } from "react-apollo";
import Tour from "helpers/tourHelper";
import "./style.scss";
import AnimatedNumber from "react-animated-number";
import PowerLine from "../JumpDrive/powerLine";
import { useSubscribeToMore } from "helpers/hooks/useQueryAndSubscribe";
import { useQuery } from "@apollo/react-hooks";
/* TODO

Some improvements:
- Make it so you can just click on a box or a yellow line to set the power level
- Make it so the names show up better (add a display name to the system class)
- Change the types of the systems to make it easier to sort the systems by name.
*/

const mutation = gql`
  mutation ChangePower($id: ID!, $level: Int!) {
    changePower(systemId: $id, power: $level)
  }
`;

const SYSTEMS_SUB = gql`
  subscription SystemsUpdate($simulatorId: ID) {
    systemsUpdate(simulatorId: $simulatorId, power: true) {
      name
      displayName
      type
      id
      power {
        power
        powerLevels
      }
      damage {
        damaged
      }
    }
  }
`;

const REACTOR_SUB = gql`
  subscription ReactorUpdate($simulatorId: ID) {
    reactorUpdate(simulatorId: $simulatorId) {
      id
      model
      batteryChargeLevel
      efficiency
      powerOutput
    }
  }
`;

const PowerDistribution = ({ client, simulator, clientObj }) => {
  const { loading, data, subscribeToMore } = useQuery(SYSTEMS_QUERY, {
    variables: {
      simulatorId: simulator.id
    }
  });
  const reactorConfig = React.useMemo(
    () => ({
      variables: {
        simulatorId: simulator.id
      },
      updateQuery: (previousResult, { subscriptionData }) => {
        return Object.assign({}, previousResult, {
          reactors: subscriptionData.data.reactorUpdate
        });
      }
    }),
    [simulator.id]
  );
  useSubscribeToMore(subscribeToMore, REACTOR_SUB, reactorConfig);
  const systemsConfig = React.useMemo(
    () => ({
      variables: {
        simulatorId: simulator.id
      },
      updateQuery: (previousResult, { subscriptionData }) => {
        return Object.assign({}, previousResult, {
          systems: subscriptionData.data.systemsUpdate
        });
      }
    }),
    [simulator.id]
  );
  useSubscribeToMore(subscribeToMore, SYSTEMS_SUB, systemsConfig);

  if (loading || !data.reactors) return null;
  // Get the batteries, get just the first one.
  const systems = data.systems;
  const battery = data.reactors.find(r => r.model === "battery");
  const reactor = data.reactors.find(r => r.model === "reactor");
  const charge = battery && battery.batteryChargeLevel;

  const trainingSteps = hasBattery =>
    [
      {
        selector: ".nothing",
        content:
          "You are responsible for distributing power to the various systems on the ship. Without enough power, systems will not run. You only have a limited amount of power to distribute, so make sure you put power in the places that need it the most."
      },
      {
        selector: ".powerlevel-containers",
        content:
          "This list shows all of the systems on the ship that take power. Drag the green bars next to each system to add or remove power. You must ensure that there is enough power for the system to run. The yellow bars represent the amount of power necessary for the system to function at a certain level."
      },
      {
        selector: ".totalPowerText",
        content:
          "This is the total amount of power the ship has available and how much is being used based on the power levels above."
      },
      hasBattery && {
        selector: ".battery-holder",
        content:
          "These are the ship’s batteries, if it has any. They show how much power is remaining in the batteries. If you use more power than is being outputted by your reactor, power will draw from the batteries. If they run out of power, you will have to balance your power, and the batteries will need to be recharged. You can recharge batteries from your reactor by using less power than the current reactor output. Don’t let these run out in the middle of space. That would be...problematic."
      }
    ].filter(Boolean);

  return (
    <Container fluid={!!battery} className="powerLevels">
      <Row className="powerlevel-row">
        <Col lg="12" xl={battery ? 8 : 12} className="powerlevel-containers">
          <Power systems={systems} client={client} reactor={reactor} />
        </Col>
        {battery && (
          <Col sm="4" className="battery-holder">
            <Card>

                <Battery
                  level={Math.min(1, Math.max(0, (charge - 0.75) * 4))}
                />
                <Battery level={Math.min(1, Math.max(0, (charge - 0.5) * 4))} />
                <Battery
                  level={Math.min(1, Math.max(0, (charge - 0.25) * 4))}
                />
                <Battery level={Math.min(1, Math.max(0, charge * 4))} />

            </Card>
          </Col>
        )}
      </Row>
      <Tour steps={trainingSteps(battery)} client={clientObj} />
    </Container>
  );
};

const Systems = React.memo(
  ({ systems, setPowerAdjust, client }) => {
    const onChange = (system, level) => {
      const variables = {
        id: system.id,
        level
      };
      client.mutate({
        mutation,
        variables
      });
    };
    return (

        {systems
          .slice(0)
          .sort((a, b) => {
            if (a.type > b.type) return 1;
            if (a.type  b.name) return 1;
            if (a.name
              (sys.power.power || sys.power.power === 0) &&
              sys.power.powerLevels.length
          )
          .map(sys => (
            <PowerLine
              key={sys.id}
              powerLevels={sys.power.powerLevels}
              power={sys.power.power}
              onChange={value => onChange(sys, value)}
              onChanging={value => setPowerAdjust(value - sys.power.power)}
              maxPower={40}
              topPower={40}
              boxWidthRatio={6}
              label={sys.displayName}
              labelClass={`${sys.damage.damaged ? "text-danger" : ""} ${
                !sys.power.powerLevels.find(p => p <= sys.power.power)
                  ? "text-dark"
                  : ""
              }`}
            />
          ))}

    );
  },
  (prevProps, nextProps) => {
    const prevPower = prevProps.systems.map(p => p.power.power);
    const nextPower = nextProps.systems.map(p => p.power.power);
    if (prevPower.join(",") !== nextPower.join(",")) return false;
    return true;
  }
);
const Summary = ({ reactor, powerTotal }) => {
  return (

      {reactor && (

          Total Power Available:{" "}
          {Math.round(reactor.efficiency * reactor.powerOutput)}

      )}

Total Power Used: {powerTotal}

      {reactor && (
        <h4
          className={` ${
            Math.round(reactor.efficiency * reactor.powerOutput) - powerTotal <
            0
              ? "text-danger"
              : ""
          }`}
        >
          Remaining Power:{" "}
          {Math.round(reactor.efficiency * reactor.powerOutput) - powerTotal}

      )}

  );
};
const Power = ({ systems, client, reactor }) => {
  const [powerAdjust, setPowerAdjust] = React.useState(0);

  const powerTotal =
    systems.reduce((prev, next) => {
      return next.power.power + prev;
    }, 0) + powerAdjust;
  React.useEffect(() => {
    if (systems) {
      setPowerAdjust(0);
    }
  }, [systems]);

  return (
    <>
      <Systems
        systems={systems}
        setPowerAdjust={setPowerAdjust}
        client={client}
      />

    </>
  );
};
const Battery = ({ level = 1 }) => {
  return (

      <div className="battery-bar" style={{ height: `${level * 100}%` }} />

        <AnimatedNumber
          value={level}
          duration={1000}
          formatValue={n => `${Math.round(n * 100)}`}
        />

  );
};
const SYSTEMS_QUERY = gql`
  query Systems($simulatorId: ID) {
    systems(simulatorId: $simulatorId, power: true) {
      name
      displayName
      type
      id
      power {
        power
        powerLevels
      }
      damage {
        damaged
      }
    }
    reactors(simulatorId: $simulatorId) {
      id
      model
      batteryChargeLevel
      efficiency
      powerOutput
    }
  }
`;

export default withApollo(PowerDistribution);
<commit_msg>fix(Systems): Properly relabels systems on the power distribution card when the name changes. Closes #2531<commit_after>import React from "react";
import gql from "graphql-tag.macro";
import { Row, Col, Container, Card } from "helpers/reactstrap";
import { withApollo } from "react-apollo";
import Tour from "helpers/tourHelper";
import "./style.scss";
import AnimatedNumber from "react-animated-number";
import PowerLine from "../JumpDrive/powerLine";
import { useSubscribeToMore } from "helpers/hooks/useQueryAndSubscribe";
import { useQuery } from "@apollo/react-hooks";
/* TODO

Some improvements:
- Make it so you can just click on a box or a yellow line to set the power level
- Make it so the names show up better (add a display name to the system class)
- Change the types of the systems to make it easier to sort the systems by name.
*/

const mutation = gql`
  mutation ChangePower($id: ID!, $level: Int!) {
    changePower(systemId: $id, power: $level)
  }
`;

const SYSTEMS_SUB = gql`
  subscription SystemsUpdate($simulatorId: ID) {
    systemsUpdate(simulatorId: $simulatorId, power: true) {
      name
      displayName
      type
      id
      power {
        power
        powerLevels
      }
      damage {
        damaged
      }
    }
  }
`;

const REACTOR_SUB = gql`
  subscription ReactorUpdate($simulatorId: ID) {
    reactorUpdate(simulatorId: $simulatorId) {
      id
      model
      batteryChargeLevel
      efficiency
      powerOutput
    }
  }
`;

const PowerDistribution = ({ client, simulator, clientObj }) => {
  const { loading, data, subscribeToMore } = useQuery(SYSTEMS_QUERY, {
    variables: {
      simulatorId: simulator.id
    }
  });
  const reactorConfig = React.useMemo(
    () => ({
      variables: {
        simulatorId: simulator.id
      },
      updateQuery: (previousResult, { subscriptionData }) => {
        return Object.assign({}, previousResult, {
          reactors: subscriptionData.data.reactorUpdate
        });
      }
    }),
    [simulator.id]
  );
  useSubscribeToMore(subscribeToMore, REACTOR_SUB, reactorConfig);
  const systemsConfig = React.useMemo(
    () => ({
      variables: {
        simulatorId: simulator.id
      },
      updateQuery: (previousResult, { subscriptionData }) => {
        return Object.assign({}, previousResult, {
          systems: subscriptionData.data.systemsUpdate
        });
      }
    }),
    [simulator.id]
  );
  useSubscribeToMore(subscribeToMore, SYSTEMS_SUB, systemsConfig);

  if (loading || !data.reactors) return null;
  // Get the batteries, get just the first one.
  const systems = data.systems;
  const battery = data.reactors.find(r => r.model === "battery");
  const reactor = data.reactors.find(r => r.model === "reactor");
  const charge = battery && battery.batteryChargeLevel;

  const trainingSteps = hasBattery =>
    [
      {
        selector: ".nothing",
        content:
          "You are responsible for distributing power to the various systems on the ship. Without enough power, systems will not run. You only have a limited amount of power to distribute, so make sure you put power in the places that need it the most."
      },
      {
        selector: ".powerlevel-containers",
        content:
          "This list shows all of the systems on the ship that take power. Drag the green bars next to each system to add or remove power. You must ensure that there is enough power for the system to run. The yellow bars represent the amount of power necessary for the system to function at a certain level."
      },
      {
        selector: ".totalPowerText",
        content:
          "This is the total amount of power the ship has available and how much is being used based on the power levels above."
      },
      hasBattery && {
        selector: ".battery-holder",
        content:
          "These are the ship’s batteries, if it has any. They show how much power is remaining in the batteries. If you use more power than is being outputted by your reactor, power will draw from the batteries. If they run out of power, you will have to balance your power, and the batteries will need to be recharged. You can recharge batteries from your reactor by using less power than the current reactor output. Don’t let these run out in the middle of space. That would be...problematic."
      }
    ].filter(Boolean);
  return (
    <Container fluid={!!battery} className="powerLevels">
      <Row className="powerlevel-row">
        <Col lg="12" xl={battery ? 8 : 12} className="powerlevel-containers">
          <Power systems={systems} client={client} reactor={reactor} />
        </Col>
        {battery && (
          <Col sm="4" className="battery-holder">
            <Card>

                <Battery
                  level={Math.min(1, Math.max(0, (charge - 0.75) * 4))}
                />
                <Battery level={Math.min(1, Math.max(0, (charge - 0.5) * 4))} />
                <Battery
                  level={Math.min(1, Math.max(0, (charge - 0.25) * 4))}
                />
                <Battery level={Math.min(1, Math.max(0, charge * 4))} />

            </Card>
          </Col>
        )}
      </Row>
      <Tour steps={trainingSteps(battery)} client={clientObj} />
    </Container>
  );
};

const Systems = React.memo(
  ({ systems, setPowerAdjust, client }) => {
    const onChange = (system, level) => {
      const variables = {
        id: system.id,
        level
      };
      client.mutate({
        mutation,
        variables
      });
    };
    return (

        {systems
          .slice(0)
          .sort((a, b) => {
            if (a.type > b.type) return 1;
            if (a.type  b.displayName) return 1;
            if (a.displayName
              (sys.power.power || sys.power.power === 0) &&
              sys.power.powerLevels.length
          )
          .map(sys => (
            <PowerLine
              key={sys.id}
              powerLevels={sys.power.powerLevels}
              power={sys.power.power}
              onChange={value => onChange(sys, value)}
              onChanging={value => setPowerAdjust(value - sys.power.power)}
              maxPower={40}
              topPower={40}
              boxWidthRatio={6}
              label={sys.displayName}
              labelClass={`${sys.damage.damaged ? "text-danger" : ""} ${
                !sys.power.powerLevels.find(p => p <= sys.power.power)
                  ? "text-dark"
                  : ""
              }`}
            />
          ))}

    );
  },
  (prevProps, nextProps) => {
    const prevPower = prevProps.systems.map(p => p.power.power);
    const nextPower = nextProps.systems.map(p => p.power.power);
    if (prevPower.join(",") !== nextPower.join(",")) return false;
    const prevDisplayName = prevProps.systems.map(p => p.displayName);
    const nextDisplayName = nextProps.systems.map(p => p.displayName);
    if (prevDisplayName.join(",") !== nextDisplayName.join(",")) return false;
    return true;
  }
);
const Summary = ({ reactor, powerTotal }) => {
  return (

      {reactor && (

          Total Power Available:{" "}
          {Math.round(reactor.efficiency * reactor.powerOutput)}

      )}

Total Power Used: {powerTotal}

      {reactor && (
        <h4
          className={` ${
            Math.round(reactor.efficiency * reactor.powerOutput) - powerTotal <
            0
              ? "text-danger"
              : ""
          }`}
        >
          Remaining Power:{" "}
          {Math.round(reactor.efficiency * reactor.powerOutput) - powerTotal}

      )}

  );
};
const Power = ({ systems, client, reactor }) => {
  const [powerAdjust, setPowerAdjust] = React.useState(0);

  const powerTotal =
    systems.reduce((prev, next) => {
      return next.power.power + prev;
    }, 0) + powerAdjust;
  React.useEffect(() => {
    if (systems) {
      setPowerAdjust(0);
    }
  }, [systems]);

  return (
    <>
      <Systems
        systems={systems}
        setPowerAdjust={setPowerAdjust}
        client={client}
      />

    </>
  );
};
const Battery = ({ level = 1 }) => {
  return (

      <div className="battery-bar" style={{ height: `${level * 100}%` }} />

        <AnimatedNumber
          value={level}
          duration={1000}
          formatValue={n => `${Math.round(n * 100)}`}
        />

  );
};
const SYSTEMS_QUERY = gql`
  query Systems($simulatorId: ID) {
    systems(simulatorId: $simulatorId, power: true) {
      name
      displayName
      type
      id
      power {
        power
        powerLevels
      }
      damage {
        damaged
      }
    }
    reactors(simulatorId: $simulatorId) {
      id
      model
      batteryChargeLevel
      efficiency
      powerOutput
    }
  }
`;

export default withApollo(PowerDistribution);
<|endoftext|>


<commit_before>(function($) {

  $.fn.tweet = function(o){
    var s = {
      username: ["seaofclouds"],                // [string]   required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
      list: null,                               // [string]   optional name of list belonging to username
      avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
      count: 3,                                 // [integer]  how many tweets to display?
      intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
      outro_text: null,                         // [string]   do you want text AFTER your tweets?
      join_text:  null,                         // [string]   optional text in between date and tweet, try setting to "auto"
      auto_join_text_default: "i said,",        // [string]   auto text for non verb: "i said" bullocks
      auto_join_text_ed: "i",                   // [string]   auto text for past tense: "i" surfed
      auto_join_text_ing: "i am",               // [string]   auto tense for present tense: "i was" surfing
      auto_join_text_reply: "i replied to",     // [string]   auto tense for replies: "i replied to" @someone "with"
      auto_join_text_url: "i was looking at",   // [string]   auto tense for urls: "i was looking at" http:...
      loading_text: null,                       // [string]   optional loading text, displayed while tweets load
      query: null,                              // [string]   optional search query
      refresh_interval: null ,                  // [integer]  optional number of seconds after which to reload tweets
      twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
      twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
      twitter_search_url: "search.twitter.com"  // [string]   custom twitter search url, if any (apigee, etc.)
    };

    if(o) $.extend(s, o);

    $.fn.extend({
      linkUrl: function() {
        var returning = [];
        var regexp = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
        this.each(function() {
          returning.push(this.replace(regexp,"$1"));
        });
        return $(returning);
      },
      linkUser: function() {
        var returning = [];
        var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp,"@$1"));        });
        return $(returning);
      },
      linkHash: function() {
        var returning = [];
        var regexp = /(?:^| )[\#]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp, ' #$1'));
        });
        return $(returning);
      },
      capAwesome: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(awesome)\b/gi, '$1'));
        });
        return $(returning);
      },
      capEpic: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(epic)\b/gi, '$1'));
        });
        return $(returning);
      },
      makeHeart: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/(<)+[3]/gi, "<tt class='heart'>♥</tt>"));
        });
        return $(returning);
      }
    });

    function parse_date(date_str) {
      // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
      // cannot handle in IE. We therefore perform the following transformation:
      // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
      return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
    }

    function relative_time(time_value) {
      var parsed_date = parse_date(time_value);
      var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
      var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
      var r = '';
      if (delta < 60) {
	r = delta + ' seconds ago';
      } else if(delta < 120) {
	r = 'a minute ago';
      } else if(delta < (45*60)) {
	r = (parseInt(delta / 60, 10)).toString() + ' minutes ago';
      } else if(delta < (2*60*60)) {
	r = 'an hour ago';
      } else if(delta < (24*60*60)) {
	r = '' + (parseInt(delta / 3600, 10)).toString() + ' hours ago';
      } else if(delta < (48*60*60)) {
	r = 'a day ago';
      } else {
	r = (parseInt(delta / 86400, 10)).toString() + ' days ago';
      }
      return 'about ' + r;
    }

    function build_url() {
      var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
      if (s.list) {
        return proto+"//"+s.twitter_api_url+"/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?";
      } else if (s.query == null && s.username.length == 1) {
        return proto+'//'+s.twitter_api_url+'/1/statuses/user_timeline.json?screen_name='+s.username[0]+'&count='+s.count+'&include_rts=1&callback=?';
      } else {
        var query = (s.query || 'from:'+s.username.join(' OR from:'));
        return proto+'//'+s.twitter_search_url+'/search.json?&q='+encodeURIComponent(query)+'&rpp='+s.count+'&callback=?';
      }
    }

    return this.each(function(i, widget){
      var list = $('
').appendTo(widget);
      var intro = '
'+s.intro_text+'
';
      var outro = '
'+s.outro_text+'
';
      var loading = $('
'+s.loading_text+'
');

      if(typeof(s.username) == "string"){
        s.username = [s.username];
      }

      if (s.loading_text) $(widget).append(loading);
      $(widget).bind("load", function(){
        $.getJSON(build_url(), function(data){
          if (s.loading_text) loading.remove();
          if (s.intro_text) list.before(intro);
          list.empty();
          var tweets = (data.results || data);
          $.each(tweets, function(i,item){
            // auto join text based on verb tense and content
            if (s.join_text == "auto") {
              if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
                var join_text = s.auto_join_text_reply;
              } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
                var join_text = s.auto_join_text_url;
              } else if (item.text.match(/^((\w+ed)|just) .*/im)) {
                var join_text = s.auto_join_text_ed;
              } else if (item.text.match(/^(\w*ing) .*/i)) {
                var join_text = s.auto_join_text_ing;
              } else {
                var join_text = s.auto_join_text_default;
              }
            } else {
              var join_text = s.join_text;
            };

            var from_user = item.from_user || item.user.screen_name;
            var profile_image_url = item.profile_image_url || item.user.profile_image_url;
            var join_template = ' '+join_text+' ';
            var join = ((s.join_text) ? join_template : ' ');
            var avatar_template = '';
            var avatar = (s.avatar_size ? avatar_template : '');
            var date = ''+relative_time(item.created_at)+'';
            var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '';

            // until we create a template option, arrange the items below to alter a tweet's display.
            list.append('
' + avatar + date + join + text + '
');

            list.children('li:first').addClass('tweet_first');
            list.children('li:odd').addClass('tweet_even');
            list.children('li:even').addClass('tweet_odd');
          });
          if (s.outro_text) list.after(outro);
          $(widget).trigger("loaded").trigger((tweets.length == 0 ? "empty" : "full"));
          if (s.refresh_interval) {
            window.setTimeout(function() { $(widget).trigger("load"); }, 1000 * s.refresh_interval);
          };
        });
      }).trigger("load");
    });
  };
})(jQuery);
<commit_msg>Include comment about origin of new url regex<commit_after>(function($) {

  $.fn.tweet = function(o){
    var s = {
      username: ["seaofclouds"],                // [string]   required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
      list: null,                               // [string]   optional name of list belonging to username
      avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
      count: 3,                                 // [integer]  how many tweets to display?
      intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
      outro_text: null,                         // [string]   do you want text AFTER your tweets?
      join_text:  null,                         // [string]   optional text in between date and tweet, try setting to "auto"
      auto_join_text_default: "i said,",        // [string]   auto text for non verb: "i said" bullocks
      auto_join_text_ed: "i",                   // [string]   auto text for past tense: "i" surfed
      auto_join_text_ing: "i am",               // [string]   auto tense for present tense: "i was" surfing
      auto_join_text_reply: "i replied to",     // [string]   auto tense for replies: "i replied to" @someone "with"
      auto_join_text_url: "i was looking at",   // [string]   auto tense for urls: "i was looking at" http:...
      loading_text: null,                       // [string]   optional loading text, displayed while tweets load
      query: null,                              // [string]   optional search query
      refresh_interval: null ,                  // [integer]  optional number of seconds after which to reload tweets
      twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
      twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
      twitter_search_url: "search.twitter.com"  // [string]   custom twitter search url, if any (apigee, etc.)
    };

    if(o) $.extend(s, o);

    $.fn.extend({
      linkUrl: function() {
        var returning = [];
        // See http://daringfireball.net/2010/07/improved_regex_for_matching_urls
        var regexp = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
        this.each(function() {
          returning.push(this.replace(regexp,"$1"));
        });
        return $(returning);
      },
      linkUser: function() {
        var returning = [];
        var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp,"@$1"));        });
        return $(returning);
      },
      linkHash: function() {
        var returning = [];
        var regexp = /(?:^| )[\#]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp, ' #$1'));
        });
        return $(returning);
      },
      capAwesome: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(awesome)\b/gi, '$1'));
        });
        return $(returning);
      },
      capEpic: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(epic)\b/gi, '$1'));
        });
        return $(returning);
      },
      makeHeart: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/(<)+[3]/gi, "<tt class='heart'>♥</tt>"));
        });
        return $(returning);
      }
    });

    function parse_date(date_str) {
      // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
      // cannot handle in IE. We therefore perform the following transformation:
      // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
      return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
    }

    function relative_time(time_value) {
      var parsed_date = parse_date(time_value);
      var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
      var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
      var r = '';
      if (delta < 60) {
	r = delta + ' seconds ago';
      } else if(delta < 120) {
	r = 'a minute ago';
      } else if(delta < (45*60)) {
	r = (parseInt(delta / 60, 10)).toString() + ' minutes ago';
      } else if(delta < (2*60*60)) {
	r = 'an hour ago';
      } else if(delta < (24*60*60)) {
	r = '' + (parseInt(delta / 3600, 10)).toString() + ' hours ago';
      } else if(delta < (48*60*60)) {
	r = 'a day ago';
      } else {
	r = (parseInt(delta / 86400, 10)).toString() + ' days ago';
      }
      return 'about ' + r;
    }

    function build_url() {
      var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
      if (s.list) {
        return proto+"//"+s.twitter_api_url+"/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?";
      } else if (s.query == null && s.username.length == 1) {
        return proto+'//'+s.twitter_api_url+'/1/statuses/user_timeline.json?screen_name='+s.username[0]+'&count='+s.count+'&include_rts=1&callback=?';
      } else {
        var query = (s.query || 'from:'+s.username.join(' OR from:'));
        return proto+'//'+s.twitter_search_url+'/search.json?&q='+encodeURIComponent(query)+'&rpp='+s.count+'&callback=?';
      }
    }

    return this.each(function(i, widget){
      var list = $('
').appendTo(widget);
      var intro = '
'+s.intro_text+'
';
      var outro = '
'+s.outro_text+'
';
      var loading = $('
'+s.loading_text+'
');

      if(typeof(s.username) == "string"){
        s.username = [s.username];
      }

      if (s.loading_text) $(widget).append(loading);
      $(widget).bind("load", function(){
        $.getJSON(build_url(), function(data){
          if (s.loading_text) loading.remove();
          if (s.intro_text) list.before(intro);
          list.empty();
          var tweets = (data.results || data);
          $.each(tweets, function(i,item){
            // auto join text based on verb tense and content
            if (s.join_text == "auto") {
              if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
                var join_text = s.auto_join_text_reply;
              } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
                var join_text = s.auto_join_text_url;
              } else if (item.text.match(/^((\w+ed)|just) .*/im)) {
                var join_text = s.auto_join_text_ed;
              } else if (item.text.match(/^(\w*ing) .*/i)) {
                var join_text = s.auto_join_text_ing;
              } else {
                var join_text = s.auto_join_text_default;
              }
            } else {
              var join_text = s.join_text;
            };

            var from_user = item.from_user || item.user.screen_name;
            var profile_image_url = item.profile_image_url || item.user.profile_image_url;
            var join_template = ' '+join_text+' ';
            var join = ((s.join_text) ? join_template : ' ');
            var avatar_template = '';
            var avatar = (s.avatar_size ? avatar_template : '');
            var date = ''+relative_time(item.created_at)+'';
            var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '';

            // until we create a template option, arrange the items below to alter a tweet's display.
            list.append('
' + avatar + date + join + text + '
');

            list.children('li:first').addClass('tweet_first');
            list.children('li:odd').addClass('tweet_even');
            list.children('li:even').addClass('tweet_odd');
          });
          if (s.outro_text) list.after(outro);
          $(widget).trigger("loaded").trigger((tweets.length == 0 ? "empty" : "full"));
          if (s.refresh_interval) {
            window.setTimeout(function() { $(widget).trigger("load"); }, 1000 * s.refresh_interval);
          };
        });
      }).trigger("load");
    });
  };
})(jQuery);
<|endoftext|>


<commit_before>///<reference path="jquery-1.6.2-vsdoc.js"/>

//Self-Executing Anonymous Function
//Adding New Functionality to Purchasing for Edit
(function (purchasing, $, undefined) {
    //Private Property
    var routingAdjusted = false;

    //Public Method
    purchasing.initEdit = function () {
        loadLineItems();
    };

    //Loads up the line litem info from ajax for this order
    function loadLineItems() {
        //Place a 'loading line items' ui block
        $.getJSON(purchasing._getOption("LoadLineItemsUrl"), null, function (result) {
            console.log(result);
            //var lineItemsContainer = $("#line-items-body");

            //Go through each line item and bind it to the ui
            for (var i = 0; i < result.lineItems.length; i++) {
                var prefix = "items[" + i + "].";

                /*
                //Now make sure there is an available line item on screen, if now trigger the creation of a new one
                if (document.getElementsByName(prefix + 'id').length == 0) {
                    $("#add-line-item").click();
                }*/

                for (var prop in result.lineItems[i]) {
                    var inputName = prefix + purchasing.lowerCaseFirstLetter(prop);

                    $(document.getElementsByName(inputName)).val(result.lineItems[i][prop]);
                }
            }
        });
    }

    purchasing.lowerCaseFirstLetter = function (w) {
        return w.charAt(0).toLowerCase() + w.slice(1);
    };

} (window.purchasing = window.purchasing || {}, jQuery));
<commit_msg>yay, now the line items are properly bound, including dynamically added ones.  Note the prices are not updated yet<commit_after>///<reference path="jquery-1.6.2-vsdoc.js"/>

//Self-Executing Anonymous Function
//Adding New Functionality to Purchasing for Edit
(function (purchasing, $, undefined) {
    //Private Property
    var routingAdjusted = false;

    //Public Method
    purchasing.initEdit = function () {
        loadLineItems();
    };

    //Loads up the line litem info from ajax for this order
    function loadLineItems() {
        //Place a 'loading line items' ui block
        $.getJSON(purchasing._getOption("LoadLineItemsUrl"), null, function (result) {
            console.log(result);
            //var lineItemsContainer = $("#line-items-body");

            //Go through each line item and bind it to the ui
            for (var i = 0; i < result.lineItems.length; i++) {
                var prefix = "items[" + i + "].";

                //Now make sure there is an available line item on screen, if now trigger the creation of a new one
                if (document.getElementsByName(prefix + 'id').length == 0) {
                    $("#add-line-item").click();
                }

                for (var prop in result.lineItems[i]) {
                    var inputName = prefix + purchasing.lowerCaseFirstLetter(prop);

                    $(document.getElementsByName(inputName)).val(result.lineItems[i][prop]);
                }
            }
        });
    }

    purchasing.lowerCaseFirstLetter = function (w) {
        return w.charAt(0).toLowerCase() + w.slice(1);
    };

} (window.purchasing = window.purchasing || {}, jQuery));
<|endoftext|>


<commit_before>'use strict';

var driver = require('../index'),
    log = console.log;

log('Welcome to the sample app for sync handling of the ds18x20 driver.\n');

log('First off, let\'s see if the ds18x20 driver is loaded.');
log('This we do by calling "driver.isDriverLoaded()":');
log('...');

var isLoaded = driver.isDriverLoaded();

if (!isLoaded) {

log('The driver is not loaded.');
log('This means we want to load the driver to be able to continue.');
log('To do so, we need to be super-user root... Let\'s try!');

try {
driver.loadDriver();
} catch (err) {
log('Seems like we are not running this script as a super-user...');
log('Sorry to say, but we will shut down since we cannot continue with the show.');
log('try to run "sudo node examples/sample-app-sync" to continue.');
log('or load the driver manually by running: "sudo modprobe w1-gpio && sudo modprobe w1-therm"\n');

log('Bye bye for now =-)');
process.exit(0);
}

log('Alright. The driver was loaded. Let\'s continue. Maybe you have some sensors connected...');

} else {

log('Alright, the driver was already loaded. Let\'s see if you have some sensors hooked up.');

}

log('So, first we want to list all sensors you have: "driver.list()":');
log('...');

var listOfDeviceIds = driver.list();

if (listOfDeviceIds.length === 0) {

log('Too bad. You don\'t seem to have any sensors hooked up to your raspberry...');
log('Please do so you can continue with this awesome sample app.');
log('Bye bye for now =-)');
process.exit(0);

}

log('Woohoo, you seem to have some sensors we can play with =-D.');
log('These devices were found:', listOfDeviceIds, '\n');

log('Not only can you list the devices.');
log('You can also measure the temperature of all sensors: "driver.getAll()":');
log('...');

var result = driver.getAll();

log('Each sensor that is hooked up to this raspberry pi is now probed in sequence for their temperature.');

log('The result:', result);

<commit_msg>examples/sample-app-sync: Fixed some sentences.<commit_after>'use strict';

var driver = require('../index'),
    log = console.log;

log('Welcome to the sample app for sync handling of the ds18x20 driver.\n');

log('First off, let\'s see if the ds18x20 driver is loaded.');
log('This we do by calling "driver.isDriverLoaded()":');
log('...');

var isLoaded = driver.isDriverLoaded();

if (!isLoaded) {

log('The driver is not loaded.');
log('This means we want to load the driver to be able to continue.');
log('To do so, we need to be super-user root... Let\'s try!');

try {
driver.loadDriver();
} catch (err) {
log('Seems like we are not running this script as a super-user...');
log('Sorry to say, but we will shut down since we cannot continue with the show.');
log('try to run "sudo node examples/sample-app-sync" to continue.');
log('or load the driver manually by running: "sudo modprobe w1-gpio && sudo modprobe w1-therm"\n');

log('Bye bye for now =-)');
process.exit(0);
}

log('Alright. The driver is loaded. Then let\'s continue. Maybe you have some sensors connected...?');

} else {

log('Alright, the driver was already loaded. Let\'s see if you have some sensors hooked up.');

}

log('So, first we want to list all sensors you have: "driver.list()":');
log('...');

var listOfDeviceIds = driver.list();

if (listOfDeviceIds.length === 0) {

log('Too bad. You don\'t seem to have any sensors hooked up to your raspberry...');
log('Please do so you can continue with this awesome sample app.');
log('Bye bye for now =-)');
process.exit(0);

}

log('Woohoo, you seem to have some sensors we can play with =-D.');
log('These devices were found:', listOfDeviceIds, '\n');

log('Not only can you list the devices.');
log('You can also measure the temperature of all sensors: "driver.getAll()":');
log('Each sensor that is hooked up to this raspberry pi is now probed in sequence for their temperature.');

log('...');

var result = driver.getAll();

log('The result:', result);

<|endoftext|>


<commit_before>var path = require('path'),
    _ = require('lodash'),
    rootPath = path.join(__dirname, '..', '..', '..'),
    engines = {
        bh: {
            tech: 'enb-bh/techs/bh-bundle',
            options: {
                sourcemap: true,
                bhOptions: {
                    jsAttrName: 'data-bem',
                    jsAttrScheme: 'json'
                }
            }
        },
        bemhtml: {
            tech: 'enb-bemxjst/techs/bemhtml'
        }
    };

module.exports = function (config) {
    config.includeConfig(rootPath);

    var module = config.module('enb-bem-tmpl-specs'),
        helperWithCoverage = module.createConfigurator('tmpl-specs-with-coverage', {
            coverage: {
                engines: ['BH'],
                exclude: ['**/*.tmpl-specs/**', '**/node_modules/**']
            }
        }),
        helperWithoutCoverage = module.createConfigurator('tmpl-specs-without-coverage');

    declareSpec('no lang', {
        langs: false,
        coverage: false
        // levels: ['common.blocks'] by default
    });

    declareSpec('custom deps', {
        depsTech: 'deps',
        langs: false,
        coverage: false
        // levels: ['common.blocks'] by default
    });

    declareSpec('no lang with coverage', {
        langs: false,
        coverage: true
    });

    declareSpec('langs: true', {
        langs: true,
        coverage: false,
        levels: ['mock-i18n.blocks']
    });

    declareSpec('langs: true with custom mock', {
        langs: true,
        coverage: false,
        levels: ['custom-mock-i18n.blocks'],
        mockI18N: function (global, bem_) {
            global.BEM = bem_;

            bem_.I18N = function (keyset, key) {
                return keyset + '::' + key;
            };
        }
    });

    declareSpec('langs: true with coverage', {
        langs: true,
        coverage: true,
        levels: ['mock-i18n.blocks']
    });

    declareSpec('many langs', {
        langs: ['ru', 'en'],
        coverage: false,
        levels: ['i18n.blocks']
    });

    declareSpec('many langs with coverage', {
        langs: ['ru', 'en'],
        coverage: true,
        levels: ['i18n.blocks']
    });

    // helper
    function declareSpec(name, opts) {
        var destPath = name.replace(/[^\w\d]+/ig, '-') + '.tmpl-specs'; // sluggified

        opts.destPath = destPath;

        (opts.coverage ? helperWithCoverage : helperWithoutCoverage).configure(_.assign({}, {
            engines: {
                'BH': opts.langs ? _.assign({}, engines.bh, {
                    options: { requires: { i18n: { globals: 'BEM.I18N' } } }
                }) : engines.bh,
                'BEMHTML dev': _.assign({}, engines.bemhtml, { options: { devMode: true } }),
                'BEMHTML prod': _.assign({}, engines.bemhtml, { options: { devMode: false } })
            },
            // levels for specs
            levels: [
                'common.blocks'
            ],
            // sourceLevels for block sources
            sourceLevels: [].concat([
                '../libs/bem-core/common.blocks',
                '../blocks'
            ], opts.levels || 'common.blocks')
        }, opts));
    }

};
<commit_msg>refactor(examples): update BEMHTML engine<commit_after>var path = require('path'),
    _ = require('lodash'),
    rootPath = path.join(__dirname, '..', '..', '..'),
    engines = {
        bh: {
            tech: 'enb-bh/techs/bh-bundle',
            options: {
                sourcemap: true,
                bhOptions: {
                    jsAttrName: 'data-bem',
                    jsAttrScheme: 'json'
                }
            }
        },
        bemhtml: {
            tech: 'enb-bemxjst/techs/bemhtml'
        }
    };

module.exports = function (config) {
    config.includeConfig(rootPath);

    var module = config.module('enb-bem-tmpl-specs'),
        helperWithCoverage = module.createConfigurator('tmpl-specs-with-coverage', {
            coverage: {
                engines: ['BH'],
                exclude: ['**/*.tmpl-specs/**', '**/node_modules/**']
            }
        }),
        helperWithoutCoverage = module.createConfigurator('tmpl-specs-without-coverage');

    declareSpec('no lang', {
        langs: false,
        coverage: false
        // levels: ['common.blocks'] by default
    });

    declareSpec('custom deps', {
        depsTech: 'deps',
        langs: false,
        coverage: false
        // levels: ['common.blocks'] by default
    });

    declareSpec('no lang with coverage', {
        langs: false,
        coverage: true
    });

    declareSpec('langs: true', {
        langs: true,
        coverage: false,
        levels: ['mock-i18n.blocks']
    });

    declareSpec('langs: true with custom mock', {
        langs: true,
        coverage: false,
        levels: ['custom-mock-i18n.blocks'],
        mockI18N: function (global, bem_) {
            global.BEM = bem_;

            bem_.I18N = function (keyset, key) {
                return keyset + '::' + key;
            };
        }
    });

    declareSpec('langs: true with coverage', {
        langs: true,
        coverage: true,
        levels: ['mock-i18n.blocks']
    });

    declareSpec('many langs', {
        langs: ['ru', 'en'],
        coverage: false,
        levels: ['i18n.blocks']
    });

    declareSpec('many langs with coverage', {
        langs: ['ru', 'en'],
        coverage: true,
        levels: ['i18n.blocks']
    });

    // helper
    function declareSpec(name, opts) {
        var destPath = name.replace(/[^\w\d]+/ig, '-') + '.tmpl-specs'; // sluggified

        opts.destPath = destPath;

        (opts.coverage ? helperWithCoverage : helperWithoutCoverage).configure(_.assign({}, {
            engines: {
                'BH': opts.langs ? _.assign({}, engines.bh, {
                    options: { requires: { i18n: { globals: 'BEM.I18N' } } }
                }) : engines.bh,
                'BEMHTML': _.assign({}, engines.bemhtml, { options: {
                    engineOptions: { xhtml: true }
                } })
            },
            // levels for specs
            levels: [
                'common.blocks'
            ],
            // sourceLevels for block sources
            sourceLevels: [].concat([
                '../libs/bem-core/common.blocks',
                '../blocks'
            ], opts.levels || 'common.blocks')
        }, opts));
    }

};
<|endoftext|>


<commit_before>#!/usr/bin/env node

// This example shows how to use node-mapnik with the
// connect http server to serve map tiles to protovis
// client
//
// expected output at zoom 0: http://goo.gl/cyGwo

var mapnik = require('mapnik')
  , mercator = require('mapnik/sphericalmercator')
  , connect = require('connect')
  , url = require('url')
  , fs = require('fs')
  , path = require('path')
  , port = 3000;

var server = connect.createServer(

  connect.logger('\033[90m:method\033[0m \033[36m:url\033[0m \033[90m:status :response-timems -> :res[Content-Type]\033[0m')
, connect.static(__dirname + '/public/')
, connect.router(function(app){

    // TILE REQUEST URL
    app.get('/:x/:y/:z', function(req, res, next){

      try {
        // CALCULATE BBOX FOR RENDER STEP
        var bbox = mercator.xyz_to_envelope(parseInt(req.params.x),
                                            parseInt(req.params.y),
                                            parseInt(req.params.z), false);

        // CREATE MAP
        var map = new mapnik.Map(256, 256, mercator.srs);
        map.load(path.join(__dirname, '../../stylesheet.xml'));
        map.zoom_all();

        // RENDER MAP AS PNG
        map.render(bbox, 'png', function(err, buffer) {
          if (err) {
            throw err;
          } else {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'image/png');
            res.end(buffer);
          }
        });
      }
      catch (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end(err.message);
      }
    });
  })
);

server.listen(port);
console.log("Server running on port " + port);<commit_msg>update comments<commit_after>#!/usr/bin/env node

// This example shows how to use node-mapnik with the
// connect http server to serve map tiles to protovis
// client
//
// expected output at zoom 0: http://goo.gl/cyGwo

var mapnik = require('mapnik')
  , mercator = require('mapnik/sphericalmercator')
  , connect = require('connect')
  , url = require('url')
  , fs = require('fs')
  , path = require('path')
  , port = 3000;

var server = connect.createServer(

  connect.logger('\033[90m:method\033[0m \033[36m:url\033[0m \033[90m:status :response-timems -> :res[Content-Type]\033[0m')
, connect.static(__dirname + '/public/')
, connect.router(function(app){

    // Tile request url
    app.get('/:x/:y/:z', function(req, res, next){

      try {
        // calculate the bounding box for each tile
        var bbox = mercator.xyz_to_envelope(parseInt(req.params.x),
                                            parseInt(req.params.y),
                                            parseInt(req.params.z), false);

        // create map
        var map = new mapnik.Map(256, 256, mercator.srs);
        map.load(path.join(__dirname, '../../stylesheet.xml'));
        map.zoom_all();

        // render map
        map.render(bbox, 'png', function(err, buffer) {
          if (err) {
            throw err;
          } else {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'image/png');
            res.end(buffer);
          }
        });
      }
      catch (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end(err.message);
      }
    });
  })
);

server.listen(port);
console.log("Server running on port " + port);<|endoftext|>


<commit_before>import React from "react";
import hljs from "highlight.js/lib/core";
import hljsJavaScriptLanguage from "highlight.js/lib/languages/javascript";
import slugify from "slugify";
import CodeExampleComponent from "../Example";

import Default from "../../examples/default";
import NoAnchorArrow from "../../examples/noAnchorArrow";
import ShowTime from "../../examples/showTime";
import ShowTimeOnly from "../../examples/showTimeOnly";
import ExcludeTimes from "../../examples/excludeTimes";
import IncludeTimes from "../../examples/includeTimes";
import InjectTimes from "../../examples/injectTimes";
import FilterTimes from "../../examples/filterTimes";
import ExcludeTimePeriod from "../../examples/excludeTimePeriod";
import CustomDateFormat from "../../examples/customDateFormat";
import CustomClassName from "../../examples/customClassName";
import CustomCalendarClassName from "../../examples/customCalendarClassName";
import CustomDayClassName from "../../examples/customDayClassName";
import CustomTimeClassName from "../../examples/customTimeClassName";
import Today from "../../examples/today";
import PlaceholderText from "../../examples/placeholderText";
import SpecificDateRange from "../../examples/specificDateRange";
import MinDate from "../../examples/minDate";
import MaxDate from "../../examples/maxDate";
import DateRangeWithShowDisabledNavigation from "../../examples/dateRangeWithShowDisabledNavigation";
import Locale from "../../examples/locale";
import LocaleWithTime from "../../examples/localeWithTime";
import LocaleWithoutGlobalVariable from "../../examples/localeWithoutGlobalVariable";
import ExcludeDates from "../../examples/excludeDates";
import HighlightDates from "../../examples/highlightDates";
import HighlightDatesRanges from "../../examples/highlightDatesRanges";
import IncludeDates from "../../examples/includeDates";
import FilterDates from "../../examples/filterDates";
import DateRange from "../../examples/dateRange";
import Disabled from "../../examples/disabled";
import DisabledKeyboardNavigation from "../../examples/disabledKeyboardNavigation";
import ReadOnly from "../../examples/readOnly";
import ClearInput from "../../examples/clearInput";
import OnBlurCallbacks from "../../examples/onBlurCallbacks";
import ConfigurePopper from "../../examples/configurePopper";
import Portal from "../../examples/portal";
import PortalById from "../../examples/portalById";
import TabIndex from "../../examples/tabIndex";
import YearDropdown from "../../examples/yearDropdown";
import YearItemNumber from "../../examples/yearItemNumber";
import MonthDropdown from "../../examples/monthDropdown";
import MonthDropdownShort from "../../examples/monthDropdownShort";
import MonthYearDropdown from "../../examples/monthYearDropdown";
import YearSelectDropdown from "../../examples/yearSelectDropdown";
import Inline from "../../examples/inline";
import OpenToDate from "../../examples/openToDate";
import FixedCalendar from "../../examples/fixedCalendar";
import WeekNumbers from "../../examples/weekNumbers";
import CustomInput from "../../examples/customInput";
import MultiMonth from "../../examples/multiMonth";
import MultiMonthPrevious from "../../examples/multiMonthPrevious";
import MultiMonthDropdown from "../../examples/multiMonthDropdown";
import MultiMonthInline from "../../examples/multiMonthInline";
import Children from "../../examples/children";
import CalendarContainer from "../../examples/calendarContainer";
import RawChange from "../../examples/rawChange";
import DontCloseOnSelect from "../../examples/dontCloseOnSelect";
import RenderCustomHeader from "../../examples/renderCustomHeader";
import RenderCustomDay from "../../examples/renderCustomDay";
import TimeInput from "../../examples/timeInput";
import StrictParsing from "../../examples/strictParsing";
import MonthPicker from "../../examples/monthPicker";
import YearPicker from "../../examples/yearPicker";
import monthPickerFullName from "../../examples/monthPickerFullName";
import monthPickerTwoColumns from "../../examples/monthPickerTwoColumns";
import RangeMonthPicker from "../../examples/rangeMonthPicker";
import QuarterPicker from "../../examples/quarterPicker";
import RangeQuarterPicker from "../../examples/rangeQuarterPicker";
import OnCalendarChangeStateCallbacks from "../../examples/onCalendarOpenStateCallbacks";
import CustomTimeInput from "../../examples/customTimeInput";
import CloseOnScroll from "../../examples/closeOnScroll";
import CloseOnScrollCallback from "../../examples/closeOnScrollCallback";
import SelectsRange from "../../examples/selectsRange";

import "./style.scss";
import "react-datepicker/dist/react-datepicker.css";

export default class exampleComponents extends React.Component {
  componentDidMount() {
    hljs.initHighlightingOnLoad();
    hljs.registerLanguage("javascript", hljsJavaScriptLanguage);
  }

  examples = [
    {
      title: "Default",
      component: Default
    },
    {
      title: "Calendar container",
      component: CalendarContainer
    },
    {
      title: "Calendar open state callbacks",
      component: OnCalendarChangeStateCallbacks
    },
    {
      title: "Children",
      component: Children
    },
    {
      title: "Clear datepicker input",
      component: ClearInput
    },
    {
      title: "Close on scroll",
      component: CloseOnScroll
    },
    {
      title: "Close on scroll callback",
      component: CloseOnScrollCallback
    },
    {
      title: "Configure Popper Properties",
      component: ConfigurePopper,
      description: (

Full docs for the popper can be found at{" "}
          popper.js.org

      )
    },
    {
      title: "Custom input",
      component: CustomInput
    },
    {
      title: "Custom header",
      component: RenderCustomHeader
    },
    {
      title: "Custom Day",
      component: RenderCustomDay
    },
    {
      title: "Custom calendar class name",
      component: CustomCalendarClassName
    },
    {
      title: "Custom class name",
      component: CustomClassName
    },
    {
      title: "Custom day class name",
      component: CustomDayClassName
    },
    {
      title: "Custom date format",
      component: CustomDateFormat
    },
    {
      title: "Custom time class name",
      component: CustomTimeClassName
    },
    {
      title: "Custom time input",
      component: CustomTimeInput
    },
    {
      title: "Date Range",
      component: DateRange
    },
    {
      title: "Date range for one datepicker",
      component: SelectsRange
    },
    {
      title: "Date Range with disabled navigation shown",
      component: DateRangeWithShowDisabledNavigation
    },
    {
      title: "Disable datepicker",
      component: Disabled
    },
    {
      title: "Disable keyboard navigation",
      component: DisabledKeyboardNavigation
    },
    {
      title: "Display Week Numbers",
      component: WeekNumbers
    },
    {
      title: "Don't hide calendar on date selection",
      component: DontCloseOnSelect
    },
    {
      title: "Exclude dates",
      component: ExcludeDates
    },
    {
      title: "Exclude Times",
      component: ExcludeTimes
    },
    {
      title: "Filter dates",
      component: FilterDates
    },
    {
      title: "Filter times",
      component: FilterTimes
    },
    {
      title: "Fixed height of Calendar",
      component: FixedCalendar
    },
    {
      title: "Get raw input value on change",
      component: RawChange
    },
    {
      title: "Highlight dates",
      component: HighlightDates
    },
    {
      title: "Highlight dates with custom class names and ranges",
      component: HighlightDatesRanges
    },
    {
      title: "Include dates",
      component: IncludeDates
    },
    {
      title: "Include Times",
      component: IncludeTimes
    },
    {
      title: "Inject Specific Times",
      component: InjectTimes
    },
    {
      title: "Inline version",
      component: Inline
    },
    {
      title: "Inline portal version",
      component: Inline
    },
    {
      title: "Input time",
      component: TimeInput
    },
    {
      title: "Locale",
      component: Locale
    },
    {
      title: "Locale with time",
      component: LocaleWithTime
    },
    {
      title: "Locale without global variables",
      component: LocaleWithoutGlobalVariable
    },
    {
      title: "Min date",
      component: MinDate
    },
    {
      title: "Max date",
      component: MaxDate
    },
    {
      title: "Month Picker",
      component: MonthPicker
    },
    {
      title: "Month Picker with Full Name",
      component: monthPickerFullName
    },
    {
      title: "Month Picker Two Columns Layout",
      component: monthPickerTwoColumns
    },
    {
      title: "Month dropdown",
      component: MonthDropdown
    },
    {
      title: "Month dropdown short month",
      component: MonthDropdownShort
    },
    {
      title: "MonthYear dropdown",
      component: MonthYearDropdown
    },
    {
      title: "Multiple months",
      component: MultiMonth
    },
    {
      title: "Multiple months with year dropdown",
      component: MultiMonthDropdown
    },
    {
      title: "Multiple months inline",
      component: MultiMonthInline
    },
    {
      title: "No Anchor Arrow",
      component: NoAnchorArrow
    },
    {
      title: "onBlur callbacks in console",
      component: OnBlurCallbacks
    },
    {
      title: "Open to date",
      component: OpenToDate
    },
    {
      title: "Placeholder text",
      component: PlaceholderText
    },
    {
      title: "Portal version",
      component: Portal
    },
    {
      title: "Portal by id",
      description:
        "If the provided portalId cannot be found in the dom, one will be created by default with that id.",
      component: PortalById
    },
    {
      title: "Quarter Picker",
      component: QuarterPicker
    },
    {
      title: "Range Month Picker",
      component: RangeMonthPicker
    },
    {
      title: "Range Quarter Picker",
      component: RangeQuarterPicker
    },
    {
      title: "Read only datepicker",
      component: ReadOnly
    },
    {
      title: "Select Time",
      component: ShowTime
    },
    {
      title: "Select Time Only",
      component: ShowTimeOnly
    },
    {
      title: "Show previous months",
      component: MultiMonthPrevious
    },
    {
      title: "Specific date range",
      component: SpecificDateRange
    },
    {
      title: "Specific Time Range",
      component: ExcludeTimePeriod
    },
    {
      title: "Strict parsing",
      component: StrictParsing
    },
    {
      title: "TabIndex",
      component: TabIndex
    },
    {
      title: "Today button",
      component: Today
    },
    {
      title: "Year Picker",
      component: YearPicker
    },
    {
      title: "Year dropdown",
      component: YearDropdown
    },
    {
      title: "Year select dropdown",
      component: YearSelectDropdown
    },
    {
      title: "Year item number",
      component: YearItemNumber
    }
  ];

  handleAnchorClick = (e, id) => {
    e.preventDefault();
    document
      .getElementById(id)
      .scrollIntoView({ behavior: "smooth", block: "center" });
  };

  render() {
    return (
      <>

Examples

          {this.examples.map((example, index) => (
            <li className="examples__navigation-item" key={`link-${index}`}>
              <a
                href={`#example-${slugify(example.title, { lower: true })}`}
                onClick={e =>
                  this.handleAnchorClick(
                    e,
                    `example-${slugify(example.title, { lower: true })}`
                  )
                }
              >
                {example.title}

          ))}

          {this.examples.map((example, index) => (
            <CodeExampleComponent key={index} example={example} />
          ))}

      </>
    );
  }
}
<commit_msg>feat: add link to url bar<commit_after>import React from "react";
import hljs from "highlight.js/lib/core";
import hljsJavaScriptLanguage from "highlight.js/lib/languages/javascript";
import slugify from "slugify";
import CodeExampleComponent from "../Example";

import Default from "../../examples/default";
import NoAnchorArrow from "../../examples/noAnchorArrow";
import ShowTime from "../../examples/showTime";
import ShowTimeOnly from "../../examples/showTimeOnly";
import ExcludeTimes from "../../examples/excludeTimes";
import IncludeTimes from "../../examples/includeTimes";
import InjectTimes from "../../examples/injectTimes";
import FilterTimes from "../../examples/filterTimes";
import ExcludeTimePeriod from "../../examples/excludeTimePeriod";
import CustomDateFormat from "../../examples/customDateFormat";
import CustomClassName from "../../examples/customClassName";
import CustomCalendarClassName from "../../examples/customCalendarClassName";
import CustomDayClassName from "../../examples/customDayClassName";
import CustomTimeClassName from "../../examples/customTimeClassName";
import Today from "../../examples/today";
import PlaceholderText from "../../examples/placeholderText";
import SpecificDateRange from "../../examples/specificDateRange";
import MinDate from "../../examples/minDate";
import MaxDate from "../../examples/maxDate";
import DateRangeWithShowDisabledNavigation from "../../examples/dateRangeWithShowDisabledNavigation";
import Locale from "../../examples/locale";
import LocaleWithTime from "../../examples/localeWithTime";
import LocaleWithoutGlobalVariable from "../../examples/localeWithoutGlobalVariable";
import ExcludeDates from "../../examples/excludeDates";
import HighlightDates from "../../examples/highlightDates";
import HighlightDatesRanges from "../../examples/highlightDatesRanges";
import IncludeDates from "../../examples/includeDates";
import FilterDates from "../../examples/filterDates";
import DateRange from "../../examples/dateRange";
import Disabled from "../../examples/disabled";
import DisabledKeyboardNavigation from "../../examples/disabledKeyboardNavigation";
import ReadOnly from "../../examples/readOnly";
import ClearInput from "../../examples/clearInput";
import OnBlurCallbacks from "../../examples/onBlurCallbacks";
import ConfigurePopper from "../../examples/configurePopper";
import Portal from "../../examples/portal";
import PortalById from "../../examples/portalById";
import TabIndex from "../../examples/tabIndex";
import YearDropdown from "../../examples/yearDropdown";
import YearItemNumber from "../../examples/yearItemNumber";
import MonthDropdown from "../../examples/monthDropdown";
import MonthDropdownShort from "../../examples/monthDropdownShort";
import MonthYearDropdown from "../../examples/monthYearDropdown";
import YearSelectDropdown from "../../examples/yearSelectDropdown";
import Inline from "../../examples/inline";
import OpenToDate from "../../examples/openToDate";
import FixedCalendar from "../../examples/fixedCalendar";
import WeekNumbers from "../../examples/weekNumbers";
import CustomInput from "../../examples/customInput";
import MultiMonth from "../../examples/multiMonth";
import MultiMonthPrevious from "../../examples/multiMonthPrevious";
import MultiMonthDropdown from "../../examples/multiMonthDropdown";
import MultiMonthInline from "../../examples/multiMonthInline";
import Children from "../../examples/children";
import CalendarContainer from "../../examples/calendarContainer";
import RawChange from "../../examples/rawChange";
import DontCloseOnSelect from "../../examples/dontCloseOnSelect";
import RenderCustomHeader from "../../examples/renderCustomHeader";
import RenderCustomDay from "../../examples/renderCustomDay";
import TimeInput from "../../examples/timeInput";
import StrictParsing from "../../examples/strictParsing";
import MonthPicker from "../../examples/monthPicker";
import YearPicker from "../../examples/yearPicker";
import monthPickerFullName from "../../examples/monthPickerFullName";
import monthPickerTwoColumns from "../../examples/monthPickerTwoColumns";
import RangeMonthPicker from "../../examples/rangeMonthPicker";
import QuarterPicker from "../../examples/quarterPicker";
import RangeQuarterPicker from "../../examples/rangeQuarterPicker";
import OnCalendarChangeStateCallbacks from "../../examples/onCalendarOpenStateCallbacks";
import CustomTimeInput from "../../examples/customTimeInput";
import CloseOnScroll from "../../examples/closeOnScroll";
import CloseOnScrollCallback from "../../examples/closeOnScrollCallback";
import SelectsRange from "../../examples/selectsRange";

import "./style.scss";
import "react-datepicker/dist/react-datepicker.css";

export default class exampleComponents extends React.Component {
  componentDidMount() {
    hljs.initHighlightingOnLoad();
    hljs.registerLanguage("javascript", hljsJavaScriptLanguage);
  }

  examples = [
    {
      title: "Default",
      component: Default
    },
    {
      title: "Calendar container",
      component: CalendarContainer
    },
    {
      title: "Calendar open state callbacks",
      component: OnCalendarChangeStateCallbacks
    },
    {
      title: "Children",
      component: Children
    },
    {
      title: "Clear datepicker input",
      component: ClearInput
    },
    {
      title: "Close on scroll",
      component: CloseOnScroll
    },
    {
      title: "Close on scroll callback",
      component: CloseOnScrollCallback
    },
    {
      title: "Configure Popper Properties",
      component: ConfigurePopper,
      description: (

Full docs for the popper can be found at{" "}
          popper.js.org

      )
    },
    {
      title: "Custom input",
      component: CustomInput
    },
    {
      title: "Custom header",
      component: RenderCustomHeader
    },
    {
      title: "Custom Day",
      component: RenderCustomDay
    },
    {
      title: "Custom calendar class name",
      component: CustomCalendarClassName
    },
    {
      title: "Custom class name",
      component: CustomClassName
    },
    {
      title: "Custom day class name",
      component: CustomDayClassName
    },
    {
      title: "Custom date format",
      component: CustomDateFormat
    },
    {
      title: "Custom time class name",
      component: CustomTimeClassName
    },
    {
      title: "Custom time input",
      component: CustomTimeInput
    },
    {
      title: "Date Range",
      component: DateRange
    },
    {
      title: "Date range for one datepicker",
      component: SelectsRange
    },
    {
      title: "Date Range with disabled navigation shown",
      component: DateRangeWithShowDisabledNavigation
    },
    {
      title: "Disable datepicker",
      component: Disabled
    },
    {
      title: "Disable keyboard navigation",
      component: DisabledKeyboardNavigation
    },
    {
      title: "Display Week Numbers",
      component: WeekNumbers
    },
    {
      title: "Don't hide calendar on date selection",
      component: DontCloseOnSelect
    },
    {
      title: "Exclude dates",
      component: ExcludeDates
    },
    {
      title: "Exclude Times",
      component: ExcludeTimes
    },
    {
      title: "Filter dates",
      component: FilterDates
    },
    {
      title: "Filter times",
      component: FilterTimes
    },
    {
      title: "Fixed height of Calendar",
      component: FixedCalendar
    },
    {
      title: "Get raw input value on change",
      component: RawChange
    },
    {
      title: "Highlight dates",
      component: HighlightDates
    },
    {
      title: "Highlight dates with custom class names and ranges",
      component: HighlightDatesRanges
    },
    {
      title: "Include dates",
      component: IncludeDates
    },
    {
      title: "Include Times",
      component: IncludeTimes
    },
    {
      title: "Inject Specific Times",
      component: InjectTimes
    },
    {
      title: "Inline version",
      component: Inline
    },
    {
      title: "Inline portal version",
      component: Inline
    },
    {
      title: "Input time",
      component: TimeInput
    },
    {
      title: "Locale",
      component: Locale
    },
    {
      title: "Locale with time",
      component: LocaleWithTime
    },
    {
      title: "Locale without global variables",
      component: LocaleWithoutGlobalVariable
    },
    {
      title: "Min date",
      component: MinDate
    },
    {
      title: "Max date",
      component: MaxDate
    },
    {
      title: "Month Picker",
      component: MonthPicker
    },
    {
      title: "Month Picker with Full Name",
      component: monthPickerFullName
    },
    {
      title: "Month Picker Two Columns Layout",
      component: monthPickerTwoColumns
    },
    {
      title: "Month dropdown",
      component: MonthDropdown
    },
    {
      title: "Month dropdown short month",
      component: MonthDropdownShort
    },
    {
      title: "MonthYear dropdown",
      component: MonthYearDropdown
    },
    {
      title: "Multiple months",
      component: MultiMonth
    },
    {
      title: "Multiple months with year dropdown",
      component: MultiMonthDropdown
    },
    {
      title: "Multiple months inline",
      component: MultiMonthInline
    },
    {
      title: "No Anchor Arrow",
      component: NoAnchorArrow
    },
    {
      title: "onBlur callbacks in console",
      component: OnBlurCallbacks
    },
    {
      title: "Open to date",
      component: OpenToDate
    },
    {
      title: "Placeholder text",
      component: PlaceholderText
    },
    {
      title: "Portal version",
      component: Portal
    },
    {
      title: "Portal by id",
      description:
        "If the provided portalId cannot be found in the dom, one will be created by default with that id.",
      component: PortalById
    },
    {
      title: "Quarter Picker",
      component: QuarterPicker
    },
    {
      title: "Range Month Picker",
      component: RangeMonthPicker
    },
    {
      title: "Range Quarter Picker",
      component: RangeQuarterPicker
    },
    {
      title: "Read only datepicker",
      component: ReadOnly
    },
    {
      title: "Select Time",
      component: ShowTime
    },
    {
      title: "Select Time Only",
      component: ShowTimeOnly
    },
    {
      title: "Show previous months",
      component: MultiMonthPrevious
    },
    {
      title: "Specific date range",
      component: SpecificDateRange
    },
    {
      title: "Specific Time Range",
      component: ExcludeTimePeriod
    },
    {
      title: "Strict parsing",
      component: StrictParsing
    },
    {
      title: "TabIndex",
      component: TabIndex
    },
    {
      title: "Today button",
      component: Today
    },
    {
      title: "Year Picker",
      component: YearPicker
    },
    {
      title: "Year dropdown",
      component: YearDropdown
    },
    {
      title: "Year select dropdown",
      component: YearSelectDropdown
    },
    {
      title: "Year item number",
      component: YearItemNumber
    }
  ];

  handleAnchorClick = (e, id) => {
    e.preventDefault();
    window.history.replaceState(null, document.title, `#${id}`);
    document
      .getElementById(id)
      .scrollIntoView({ behavior: "smooth", block: "center" });
  };

  render() {
    return (
      <>

Examples

          {this.examples.map((example, index) => (
            <li className="examples__navigation-item" key={`link-${index}`}>
              <a
                href={`#example-${slugify(example.title, { lower: true })}`}
                title="something"
                onClick={e =>
                  this.handleAnchorClick(
                    e,
                    `example-${slugify(example.title, { lower: true })}`
                  )
                }
              >
                {example.title}

          ))}

          {this.examples.map((example, index) => (
            <CodeExampleComponent key={index} example={example} />
          ))}

      </>
    );
  }
}
<|endoftext|>


<commit_before>const webpack = require('webpack')
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const IgnoreAssetsWebpackPlugin = require('@goldinteractive/ignore-assets-webpack-plugin')
const jsonImporter = require('node-sass-json-importer')
const dotenv = require('dotenv')

const appConfiguration = require('./app-configuration')

const frontendFolder = 'frontend'

const root = path.join(__dirname, '..')
const base = path.join(root, frontendFolder)
const jsBase = path.join(base, 'js')
const cssBase = path.join(base, 'css')
const publicPath = path.join(base, '_public')

const ASSET_HASH_REGEX = '@ASSET_HASH'
// which file types should be checked for asset hash regex?
const ASSET_HASH_REPLACEMENT_FILE_TYPES_WHITELIST = [
  '.html',
  '.twig',
  '.php',
  '.js',
  '.jsx',
  '.css'
]

const sassLoader = {
  loader: 'sass-loader',
  options: {
    includePaths: [cssBase, 'node_modules'],
    importer: jsonImporter
  }
}

const postcssLoader = {
  loader: 'postcss-loader',
  options: {
    config: {
      path: '.config/postcss.config.js'
    }
  }
}

// Note that svg fonts won't be picked up by this loader
const fontLoader = () => ({
  test: /\.(ttf|eot|woff|woff2)$/,
  use: {
    loader: 'file-loader',
    options: {
      name: 'fonts/[name].[ext]'
    }
  }
})

const imageLoader = ({ isProd, assetHash }) => ({
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    {
      loader: 'file-loader',
      options: {
        name: isProd ? '[hash].[ext]' : '[path][name].[ext]',
        outputPath: assetHash + '/files/'
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        disable: true, // disabled in dev mode
        optipng: {
          enabled: false
        },
        pngquant: {
          quality: '90-95'
        }
      }
    }
  ]
})

const buildBaseConfig = ({ isProd }) => ({
  watch: !isProd,
  bail: isProd,
  target: 'web',
  performance: {
    maxEntrypointSize: 700000,
    maxAssetSize: 700000
  },
  stats: isProd ? 'minimal' : 'normal'
})

const pushIfSet = (bool, entry, array = []) => {
  if (bool) {
    return array.concat([entry])
  }
  return array
}

const buildBasePlugins = ({ isProd }) =>
  pushIfSet(
    isProd,
    new webpack.BannerPlugin({
      banner: `Gold Interactive GmbH - www.goldinteractive.ch - ${new Date().getFullYear()}`
    })
  )

const buildCssConfig = (cssEntry, { assetHash, isProd, assetDir }) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    [cssEntry]: path.join(cssBase, cssEntry + '.scss')
  },
  resolve: {
    modules: [cssBase, base, 'node_modules']
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        test: /\.s?css$/,
        use: [
          'file-loader?name=[name].css',
          'extract-loader',
          'css-loader',
          postcssLoader,
          sassLoader
        ]
      },
      fontLoader(),
      imageLoader({ isProd, assetHash })
    ]
  },
  plugins: [
    ...buildBasePlugins({
      isProd
    }),
    new IgnoreAssetsWebpackPlugin({
      ignore: cssEntry + '.js'
    })
  ]
})

const buildJsConfig = (jsEntry, { assetHash, isProd, assetDir }) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    [jsEntry]: path.join(jsBase, jsEntry + '.js')
  },
  resolve: {
    extensions: ['.js', '.json', '.jsx'],
    modules: [jsBase, base, 'node_modules']
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        }
      },
      {
        test: /\.s?css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          postcssLoader,
          sassLoader
        ]
      },
      fontLoader(),
      imageLoader({ isProd })
    ]
  },
  plugins: [
    ...buildBasePlugins({
      isProd
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ]
})

const buildProjectSkeletonConfig = ({
  jsEntries,
  assetHash,
  isProd,
  baseOutputDir,
  assetDir,
  assetHashTemplateReplacePath
}) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    // Webpack needs an entry...
    [jsEntries[0] + '-omit']: path.join(jsBase, jsEntries[0] + '.js')
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        // match all
        test: /[\s\S]*/,
        use: 'null-loader'
      }
    ]
  },
  plugins: [
    new IgnoreAssetsWebpackPlugin({
      ignore: jsEntries[0] + '-omit.js'
    }),
    ...buildBasePlugins({
      isProd
    }),
    new CopyWebpackPlugin([
      {
        from: publicPath,
        to: '..',
        transform: (content, pathname) => {
          if (assetHashTemplateReplacePath) {
            const fullPath = path.join(root, assetHashTemplateReplacePath)
            // One must not stringify binary files such as png, glb, etc.
            const isInFileTypeWhiteList =
              ASSET_HASH_REPLACEMENT_FILE_TYPES_WHITELIST.find(fileType =>
                pathname.endsWith(fileType)
              ) !== undefined
            if (isInFileTypeWhiteList && pathname.startsWith(fullPath)) {
              const regex = new RegExp(ASSET_HASH_REGEX, 'g')
              return content.toString().replace(regex, assetHash)
            }
          }
          return content
        }
      }
    ])
  ]
})

module.exports = env => {
  const isProd = env.mode === 'production'
  const assetHash = env.assetHash
  const environment = env.environment
  const publicDest = env.publicDest
  const assetHashTemplateReplacePath = env.assetHashTemplateReplacePath

  // parses current .env file and provides all variables in process.env.XYZ
  // usage: process.env.APP_URL
  dotenv.config({
    path: path.join(base, '.env.' + environment)
  })

  const baseOutputDir = path.join(root, publicDest)
  const assetDir = baseOutputDir + '/' + assetHash
  const { jsEntries, cssEntries } = appConfiguration(env)

  const configObject = {
    baseOutputDir,
    assetDir,
    assetHash,
    assetHashTemplateReplacePath,
    isProd,
    jsEntries
  }

  return [
    buildProjectSkeletonConfig(configObject),
    ...jsEntries.map(jsEntry => buildJsConfig(jsEntry, configObject)),
    ...cssEntries.map(cssEntry => buildCssConfig(cssEntry, configObject))
  ]
}
<commit_msg>Improve path handling for image loader<commit_after>const webpack = require('webpack')
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const IgnoreAssetsWebpackPlugin = require('@goldinteractive/ignore-assets-webpack-plugin')
const jsonImporter = require('node-sass-json-importer')
const dotenv = require('dotenv')

const appConfiguration = require('./app-configuration')

const frontendFolder = 'frontend'

const root = path.join(__dirname, '..')
const base = path.join(root, frontendFolder)
const jsBase = path.join(base, 'js')
const cssBase = path.join(base, 'css')
const publicPath = path.join(base, '_public')

const ASSET_HASH_REGEX = '@ASSET_HASH'
// which file types should be checked for asset hash regex?
const ASSET_HASH_REPLACEMENT_FILE_TYPES_WHITELIST = [
  '.html',
  '.twig',
  '.php',
  '.js',
  '.jsx',
  '.css'
]

const sassLoader = {
  loader: 'sass-loader',
  options: {
    includePaths: [cssBase, 'node_modules'],
    importer: jsonImporter
  }
}

const postcssLoader = {
  loader: 'postcss-loader',
  options: {
    config: {
      path: '.config/postcss.config.js'
    }
  }
}

// Note that svg fonts won't be picked up by this loader
const fontLoader = () => ({
  test: /\.(ttf|eot|woff|woff2)$/,
  use: {
    loader: 'file-loader',
    options: {
      name: 'fonts/[name].[ext]'
    }
  }
})

const imageLoader = ({ isProd, assetHash }) => ({
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    {
      loader: 'file-loader',
      options: {
        name: isProd ? '[hash].[ext]' : '[path][name].[ext]',
        outputPath: '/files/',
        publicPath: assetHash + '/files/',
        context: 'frontend'
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        disable: true, // disabled in dev mode
        optipng: {
          enabled: false
        },
        pngquant: {
          quality: '90-95'
        }
      }
    }
  ]
})

const buildBaseConfig = ({ isProd }) => ({
  watch: !isProd,
  bail: isProd,
  target: 'web',
  performance: {
    maxEntrypointSize: 700000,
    maxAssetSize: 700000
  },
  stats: isProd ? 'minimal' : 'normal'
})

const pushIfSet = (bool, entry, array = []) => {
  if (bool) {
    return array.concat([entry])
  }
  return array
}

const buildBasePlugins = ({ isProd }) =>
  pushIfSet(
    isProd,
    new webpack.BannerPlugin({
      banner: `Gold Interactive GmbH - www.goldinteractive.ch - ${new Date().getFullYear()}`
    })
  )

const buildCssConfig = (cssEntry, { assetHash, isProd, assetDir }) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    [cssEntry]: path.join(cssBase, cssEntry + '.scss')
  },
  resolve: {
    modules: [cssBase, base, 'node_modules']
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        test: /\.s?css$/,
        use: [
          'file-loader?name=[name].css',
          'extract-loader',
          'css-loader',
          postcssLoader,
          sassLoader
        ]
      },
      fontLoader(),
      imageLoader({ isProd, assetHash })
    ]
  },
  plugins: [
    ...buildBasePlugins({
      isProd
    }),
    new IgnoreAssetsWebpackPlugin({
      ignore: cssEntry + '.js'
    })
  ]
})

const buildJsConfig = (jsEntry, { assetHash, isProd, assetDir }) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    [jsEntry]: path.join(jsBase, jsEntry + '.js')
  },
  resolve: {
    extensions: ['.js', '.json', '.jsx'],
    modules: [jsBase, base, 'node_modules']
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        }
      },
      {
        test: /\.s?css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          postcssLoader,
          sassLoader
        ]
      },
      fontLoader(),
      imageLoader({ isProd, assetHash })
    ]
  },
  plugins: [
    ...buildBasePlugins({
      isProd
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ]
})

const buildProjectSkeletonConfig = ({
  jsEntries,
  assetHash,
  isProd,
  baseOutputDir,
  assetDir,
  assetHashTemplateReplacePath
}) => ({
  ...buildBaseConfig({
    isProd
  }),
  entry: {
    // Webpack needs an entry...
    [jsEntries[0] + '-omit']: path.join(jsBase, jsEntries[0] + '.js')
  },
  output: {
    filename: '[name].js',
    path: assetDir
  },
  module: {
    rules: [
      {
        // match all
        test: /[\s\S]*/,
        use: 'null-loader'
      }
    ]
  },
  plugins: [
    new IgnoreAssetsWebpackPlugin({
      ignore: jsEntries[0] + '-omit.js'
    }),
    ...buildBasePlugins({
      isProd
    }),
    new CopyWebpackPlugin([
      {
        from: publicPath,
        to: '..',
        transform: (content, pathname) => {
          if (assetHashTemplateReplacePath) {
            const fullPath = path.join(root, assetHashTemplateReplacePath)
            // One must not stringify binary files such as png, glb, etc.
            const isInFileTypeWhiteList =
              ASSET_HASH_REPLACEMENT_FILE_TYPES_WHITELIST.find(fileType =>
                pathname.endsWith(fileType)
              ) !== undefined
            if (isInFileTypeWhiteList && pathname.startsWith(fullPath)) {
              const regex = new RegExp(ASSET_HASH_REGEX, 'g')
              return content.toString().replace(regex, assetHash)
            }
          }
          return content
        }
      }
    ])
  ]
})

module.exports = env => {
  const isProd = env.mode === 'production'
  const assetHash = env.assetHash
  const environment = env.environment
  const publicDest = env.publicDest
  const assetHashTemplateReplacePath = env.assetHashTemplateReplacePath

  // parses current .env file and provides all variables in process.env.XYZ
  // usage: process.env.APP_URL
  dotenv.config({
    path: path.join(base, '.env.' + environment)
  })

  const baseOutputDir = path.join(root, publicDest)
  const assetDir = baseOutputDir + '/' + assetHash
  const { jsEntries, cssEntries } = appConfiguration(env)

  const configObject = {
    baseOutputDir,
    assetDir,
    assetHash,
    assetHashTemplateReplacePath,
    isProd,
    jsEntries
  }

  return [
    buildProjectSkeletonConfig(configObject),
    ...jsEntries.map(jsEntry => buildJsConfig(jsEntry, configObject)),
    ...cssEntries.map(cssEntry => buildCssConfig(cssEntry, configObject))
  ]
}
<|endoftext|>