<commit_before>/**
 * Created by remy on 22/04/15.
 */

var chart;
var chartData;
var pieData;

function getBattery(callback) {
    var data;
    jQuery.ajax({
        url: "http://idp-api.herokuapp.com/spin/archive/batterij",
        type: "GET",
        dataType: 'json',
        success: function (resultData) {
            callback(resultData);
            //data = resultData;
            console.log(resultData);
        }, error: function (jqXHR, textStatus, errorThrown) {
            callback(null);
            console.log(errorThrown);
        }
    });
}

function getModes(callback) {
    var data;
    jQuery.ajax({
        url: "http://idp-api.herokuapp.com/spin/archive/mode",
        type: "GET",
        dataType: 'json',
        success: function (resultData) {
            callback(resultData);
            //data = resultData;
            //console.log(resultData);
        }, error: function (jqXHR, textStatus, errorThrown) {
            callback(null);
            console.log(errorThrown);
        }
    });
}

function generateData() {
    var data = getBattery(function (data) {
        chartData = new Array(data.length);
        for (var i = 0; i < data.length; i++) {
            chartData[i] = parseInt(data[i]);
        }
        console.log(chartData);
        createChart();
    });

}

function generatePieData() {
    var data = getModes(function (data) {
        pieData = [0,0,0]
        for (var i = 0; i < data.length; i++) {
            if(data[i] == "dansen")
            {
                pieData[0]++;
                console.log(pieData[0]);
            }
            else if(data[i] == "lopen")
            {
                pieData[1]++;
            }
            else
            {
                pieData[2]++;
            }
        }
        console.log(pieData);
        createPie(pieData);
    });
}

function createPie(datas)
{
    console.log(datas);
    var ctx = document.getElementById('myChart').getContext('2d');
    var data = [
        {
            value: datas[0],
            color:"#F7464A",
            highlight: "#FF5A5E",
            label: "dansen"
        },
        {
            value: datas[1],
            color: "#46BFBD",
            highlight: "#5AD3D1",
            label: "lopen"
        },
        {
            value: datas[2],
            color: "#FDB45C",
            highlight: "#FFC870",
            label: "autonoom"
        }
    ]

    var options = {
        //Boolean - Whether we should show a stroke on each segment
        segmentShowStroke : true,

        //String - The colour of each segment stroke
        segmentStrokeColor : "#fff",

        //Number - The width of each segment stroke
        segmentStrokeWidth : 2,

        //Number - The percentage of the chart that we cut out of the middle
        percentageInnerCutout : 0, // This is 0 for Pie charts

        //Number - Amount of animation steps
        animationSteps : 100,

        //String - Animation easing effect
        animationEasing : "easeOutBounce",

        //Boolean - Whether we animate the rotation of the Doughnut
        animateRotate : true,

        //Boolean - Whether we animate scaling the Doughnut from the centre
        animateScale : false

    }
    // For a pie chart
    var myPieChart = new Chart(ctx).Pie(data,options);
}

function createChart()
{
    var options = {scaleShowLabels : false};

    var buyerData = {
        labels : ["-5","-4","-3","-2","-1","latest"],
        datasets : [
            {
                fillColor : "rgba(172,194,132,0.4)",
                strokeColor : "#ACC26D",
                pointColor : "#fff",
                pointStrokeColor : "#9DB86D",
                data : chartData
            }
        ]
    };
    var buyers = document.getElementById('buyers').getContext('2d');
     chart = new Chart(buyers).Line(buyerData, options);
}

function createrad()
{
    var ctx = document.getElementById('ding').getContext('2d');
    var options = {
        //Boolean - Whether to show lines for each scale point
        scaleShowLine : true,

        //Boolean - Whether we show the angle lines out of the radar
        angleShowLineOut : true,

        //Boolean - Whether to show labels on the scale
        scaleShowLabels : false,

        // Boolean - Whether the scale should begin at zero
        scaleBeginAtZero : true,

        //String - Colour of the angle line
        angleLineColor : "rgba(0,0,0,.1)",

        //Number - Pixel width of the angle line
        angleLineWidth : 1,

        //String - Point label font declaration
        pointLabelFontFamily : "'Arial'",

        //String - Point label font weight
        pointLabelFontStyle : "normal",

        //Number - Point label font size in pixels
        pointLabelFontSize : 10,

        //String - Point label font colour
        pointLabelFontColor : "#666",

        //Boolean - Whether to show a dot for each point
        pointDot : true,

        //Number - Radius of each point dot in pixels
        pointDotRadius : 3,

        //Number - Pixel width of point dot stroke
        pointDotStrokeWidth : 1,

        //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
        pointHitDetectionRadius : 20,

        //Boolean - Whether to show a stroke for datasets
        datasetStroke : true,

        //Number - Pixel width of dataset stroke
        datasetStrokeWidth : 2,

        //Boolean - Whether to fill the dataset with a colour
        datasetFill : true,

        //String - A legend template
        legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%>
<span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"

    }
    var data = {
        labels: ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"],
        datasets: [
            {
                label: "My First dataset",
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data: [65, 59, 90, 81, 56, 55, 40]
            },
            {
                label: "My Second dataset",
                fillColor: "rgba(151,187,205,0.2)",
                strokeColor: "rgba(151,187,205,1)",
                pointColor: "rgba(151,187,205,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(151,187,205,1)",
                data: [28, 48, 40, 19, 96, 27, 100]
            }
        ]
    };
    var myRadarChart = new Chart(ctx).Radar(data, options);
}<commit_msg>removed animation after first generation of charts<commit_after>/**
 * Created by remy on 22/04/15.
 */

var chart;
var chartData;
var pieData;

function getBattery(callback) {
    var data;
    jQuery.ajax({
        url: "http://idp-api.herokuapp.com/spin/archive/batterij",
        type: "GET",
        dataType: 'json',
        success: function (resultData) {
            callback(resultData);
            //data = resultData;
            console.log(resultData);
        }, error: function (jqXHR, textStatus, errorThrown) {
            callback(null);
            console.log(errorThrown);
        }
    });
}

function getModes(callback) {
    var data;
    jQuery.ajax({
        url: "http://idp-api.herokuapp.com/spin/archive/mode",
        type: "GET",
        dataType: 'json',
        success: function (resultData) {
            callback(resultData);
            //data = resultData;
            //console.log(resultData);
        }, error: function (jqXHR, textStatus, errorThrown) {
            callback(null);
            console.log(errorThrown);
        }
    });
}

function generateData() {
    var data = getBattery(function (data) {
        chartData = new Array(data.length);
        for (var i = 0; i < data.length; i++) {
            chartData[i] = parseInt(data[i]);
        }
        console.log(chartData);
        createChart();
    });

}

function generatePieData() {
    var data = getModes(function (data) {
        pieData = [0,0,0]
        for (var i = 0; i < data.length; i++) {
            if(data[i] == "dansen")
            {
                pieData[0]++;
                console.log(pieData[0]);
            }
            else if(data[i] == "lopen")
            {
                pieData[1]++;
            }
            else
            {
                pieData[2]++;
            }
        }
        console.log(pieData);
        createPie(pieData);
    });
}

function createPie(datas)
{
    console.log(datas);
    var ctx = document.getElementById('myChart').getContext('2d');
    var data = [
        {
            value: datas[0],
            color:"#F7464A",
            highlight: "#FF5A5E",
            label: "dansen"
        },
        {
            value: datas[1],
            color: "#46BFBD",
            highlight: "#5AD3D1",
            label: "lopen"
        },
        {
            value: datas[2],
            color: "#FDB45C",
            highlight: "#FFC870",
            label: "autonoom"
        }
    ]

    var options = {
        //Boolean - Whether we should show a stroke on each segment
        segmentShowStroke : true,

        //String - The colour of each segment stroke
        segmentStrokeColor : "#fff",

        //Number - The width of each segment stroke
        segmentStrokeWidth : 2,

        //Number - The percentage of the chart that we cut out of the middle
        percentageInnerCutout : 0, // This is 0 for Pie charts

        //Number - Amount of animation steps
        animationSteps : 100,

        //String - Animation easing effect
        animationEasing : "easeOutBounce",

        //Boolean - Whether we animate the rotation of the Doughnut
        animateRotate : true,

        //Boolean - Whether we animate scaling the Doughnut from the centre
        animateScale : false

    }

    // clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // For a pie chart
    var myPieChart = new Chart(ctx).Pie(data,options);
}

function createChart()
{
    var options = {scaleShowLabels : false};

    var buyerData = {
        labels : ["-5","-4","-3","-2","-1","latest"],
        datasets : [
            {
                fillColor : "rgba(172,194,132,0.4)",
                strokeColor : "#ACC26D",
                pointColor : "#fff",
                pointStrokeColor : "#9DB86D",
                data : chartData
            }
        ]
    };
    var buyers = document.getElementById('buyers').getContext('2d');
    // clear canvas
    buyers.clearRect(0, 0, canvas.width, canvas.height);
    chart = new Chart(buyers).Line(buyerData, options);
}

function createrad()
{
    var ctx = document.getElementById('ding').getContext('2d');
    var options = {
        //Boolean - Whether to show lines for each scale point
        scaleShowLine : true,

        //Boolean - Whether we show the angle lines out of the radar
        angleShowLineOut : true,

        //Boolean - Whether to show labels on the scale
        scaleShowLabels : false,

        // Boolean - Whether the scale should begin at zero
        scaleBeginAtZero : true,

        //String - Colour of the angle line
        angleLineColor : "rgba(0,0,0,.1)",

        //Number - Pixel width of the angle line
        angleLineWidth : 1,

        //String - Point label font declaration
        pointLabelFontFamily : "'Arial'",

        //String - Point label font weight
        pointLabelFontStyle : "normal",

        //Number - Point label font size in pixels
        pointLabelFontSize : 10,

        //String - Point label font colour
        pointLabelFontColor : "#666",

        //Boolean - Whether to show a dot for each point
        pointDot : true,

        //Number - Radius of each point dot in pixels
        pointDotRadius : 3,

        //Number - Pixel width of point dot stroke
        pointDotStrokeWidth : 1,

        //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
        pointHitDetectionRadius : 20,

        //Boolean - Whether to show a stroke for datasets
        datasetStroke : true,

        //Number - Pixel width of dataset stroke
        datasetStrokeWidth : 2,

        //Boolean - Whether to fill the dataset with a colour
        datasetFill : true,

        //String - A legend template
        legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%>
<span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"

    }
    var data = {
        labels: ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"],
        datasets: [
            {
                label: "My First dataset",
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data: [65, 59, 90, 81, 56, 55, 40]
            },
            {
                label: "My Second dataset",
                fillColor: "rgba(151,187,205,0.2)",
                strokeColor: "rgba(151,187,205,1)",
                pointColor: "rgba(151,187,205,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(151,187,205,1)",
                data: [28, 48, 40, 19, 96, 27, 100]
            }
        ]
    };
    // clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    var myRadarChart = new Chart(ctx).Radar(data, options);
}<|endoftext|>


<commit_before>/* global Vue */

// ChildNode.remove() polyfill for Internet Explorer
// from: https://github.com/jserz/js_piece/blob/master/DOM/ChildNode/remove()/remove().md
(function (arr) {
  arr.forEach(function (item) {
    item.remove = item.remove || function () {
      this.parentNode.removeChild(this);
    };
  });
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);

Vue.component("WelcomeBox", {
  template: `

Hi there!

        <button @click="onDismiss" class="delete"></button>

          This application allows you to create fresh new
          IIIF
          manifests from
          your dusty old
          METS/MODS files.
          Having IIIF manifests opens up a lot of possibilities:

You can now create and share annotations on your digitized works
              (they are stored on our server, but they can be used from anywhere)

Since IIIF is Linked Data all the way down, you get free permalinks
              to all parts of the object

With Mirador, you can compare objects from multiple sources
              side-by-side in the same window

          The application is currently a work-in-progress and has only been
          tested with METS/MODS files that follow the recommendations of
          the German Research Foundation (DFG). If you want to give it a spin,
          pick an object from the Central Directory of
          Digitized Prints and put the URL to the METS in the input above.

          Since the venerable DFG-Viewer
          is based on METS, you can also put a DFG-Viewer URL into the input box
          and the application will extract the URL of the corresponding METS
          document by itself.

`,
  methods: {
    onDismiss: function() {
      this.$emit('dismiss');
    }
  }
});

Vue.component("JobDisplay", {
  props: ['job'],
  data: function() {
    return {
      queueLength: this.job.position + 1
    };
  },
  template: `

      <ErrorDisplay v-if="job.status === 'failed'"
                    :traceback="job.traceback"
                    @dismiss="triggerClose" />

{{ job.label || job.metsurl }}

                {{ job.status }}

                <progress class="progress" :value="completionRatio" max="1">
                  {{ job.status }}
                </progress>

              <input @click="onMetsUrlClick" :value="job.result" type="url"
                     class="input metsurl" ref="metsurl" readonly>

                Open in viewer

`,
  computed: {
    showProgressBar: function() {
      return (this.job.status === 'queued' || this.job.status === 'started');
    },
    completionRatio: function() {
      if (this.job.status === 'queued') {
        return ((this.queueLength + 1) - (this.job.position + 1)) / (this.queueLength + 1);
      } else if (this.job.status === 'started') {
        return this.job.current_image / this.job.total_images;
      } else {
        return null;
      }
    },
    viewerUrl: function() {
      if (this.job.result) {
        var manifestId = this.job.result.replace(/.*?\/iiif\/(.*?)\/manifest/, '$1')
        return `/view/${manifestId}`;
      }
    }
  },
  methods: {
    triggerClose: function() {
      this.$emit('dismiss-job', this.job.id);
    },
    onMetsUrlClick: function() {
      this.$refs.metsurl.setSelectionRange(0, this.$refs.metsurl.value.length);
    }
  }
});

Vue.component("NotificationForm", {
  props: ['jobIds'],
  data: function() {
    return {
      viewForm: false,
      recipient: '',
      wasSubmitted: false,
      errorMessage: null,
      invalid: false,
      isLoading: false
    };
  },
  template: `

          <input v-model="viewForm" type="checkbox"> Notify me via email

        <input v-model="recipient" :class="{'is-danger': isDisabled}"
               @invalid="invalidate" @click="removeError"
               class="input" type="email" placeholder="Enter your email">
        <button @click="registerForNotifications" :disabled="isDisabled"
                :class="{'is-loading': isLoading}"
                type="submit" class="button">Submit</button>
        {{ errorMessage }}\

        <button @click="dismiss" class="delete"></button>
        You will be notified at {{ recipient }} once the manifests are finished

`,
  computed: {
    isDisabled: function() {
      return this.invalid || this.errorMessage !== null;
    }
  },
  methods: {
    invalidate: function() {
      this.invalid = true;
    },
    removeError: function() {
      this.errorMessage = null;
      this.invalid = false;
    },
    registerForNotifications: function() {
      var vm = this;
      vm.isLoading = true;
      axios.post('/api/tasks/notify', {recipient: this.recipient,
                                       jobs: this.jobIds})
        .then(function(resp) {
          vm.wasSubmitted = true;
          vm.$emit('subscribe-to-notifications', vm.recipient);
          vm.isLoading = false;
        })
        .catch(function(err) {
          if (err.response) {
            vm.errorMessage = err.response.data.message;
          } else {
            console.error(err);
          }
          vm.isLoading = false;
        });
    },
    dismiss: function() {
      this.$emit('dismiss-notification');
    }
  }
});

Vue.component("ErrorDisplay", {
  props: ['metsUrl', 'traceback'],
  data: function() {
    return {
      showTraceback: false,
      url: this.metsUrl
    };
  },
  template: `

Could not import METS

        <button @click="onDismiss" class="delete"></button>

            Unfortunately we were unable to generate a IIIF manifest from the METS
            located at

            {{ url }}.

            The error was logged in our backend and will be examined. If you wish
            to help with debugging, you can consult the traceback below and open
            an issue on GitHub.

          <button class="button is-danger" @click="toggleTraceback">Traceback</button>

{{ traceback }}

`,
  methods: {
    onDismiss: function() {
      this.$emit('dismiss');
    },
    toggleTraceback: function() {
      this.showTraceback = !this.showTraceback;
    }
  }
});

Vue.component("MetsForm", {
  props: ['jobIds'],
  data: function() {
    return {
      metsUrl: '',
      errorMessage: null,
      traceback: null,
      showTraceback: false,
      invalid: false,
      showNotificationForm: true,
      subscribedToNotifications: false,
      isLoading: false
    };
  },
  template: `

      <NotificationForm v-if="showNotificationForm && hasJobs" :jobIds="jobIds"
                        @subscribe-to-notifications="onSubscribeToNotifications"
                        @dismiss-notification="onDismissNotification"/>
      <form @submit.prevent>

          <input v-model="metsUrl" type="url" class="input is-large"
                  @click="removeError" name="metsUrl" @invalid="invalidate"
                  placeholder="Put a METS (or DFG-Viewer) URL in here!"
                  :class="{'is-danger': isDisabled}">
          <button @click="submitUrl" class="button is-primary is-large iiif-btn"
                  type="submit" :disabled="isDisabled"
                  :class="{'is-disabled': isDisabled, 'is-loading': isLoading}">

          </button>

        {{ errorMessage }}
      </form>
      <ErrorDisplay v-if="!errorMessage && traceback" @dismiss="onDismissError"
                    :metsUrl="metsUrl" :traceback="traceback" />

`,
  computed: {
    isDisabled: function() {
      return this.invalid || this.errorMessage !== null;
    },
    hasJobs: function() {
      return this.jobIds.length > 0;
    }
  },
  methods: {
    invalidate: function() {
      this.invalid = true;
    },
    removeError: function() {
      this.invalid = false;
      this.errorMessage = null;
    },
    onDismissError: function() {
      this.invalid = false;
      this.errorMessage = null;
      this.traceback = null;
    },
    submitUrl: function() {
      var vm = this;
      vm.isLoading = true;
      axios.post('/api/import', {url: this.metsUrl})
        .then(function(resp) {
          vm.errorMessage = null;
          vm.metsUrl = '';
          vm.$emit("new-job", resp.data);
          if (vm.subscribedToNotifications) {
            axios.post('/api/tasks/notify', {recipient: vm.subscriptionAddress,
                                             job_ids: [resp.data.id]});
          }
          vm.isLoading = false;
        })
        .catch(function(err) {
          if (err.response && err.response.data.message) {
            vm.errorMessage = err.response.data.message;
          } else if (err.response && err.response.data.traceback) {
            vm.traceback = err.response.data.traceback;
          } else {
            console.error(err);
          }
          vm.isLoading = false;
        });
    },
    onDismissNotification: function() {
      this.showNotificationForm = false;
    },
    onSubscribeToNotifications: function(recipient) {
      this.subscribedToNotifications = true;
      this.subscriptionAddress = recipient;
    }
  }
});

var app = new Vue({
  data: {
    jobIds: [],  // to store the order the jobs were added in
    jobs: {},
    streams: {},
    showWelcome: !localStorage.getItem('hideWelcome')
  },
  template: `

              demetsiiify

          <input type="checkbox" class="is-hidden" id="nav-toggle-state" />

            Recent Imports
            API
            About

          <MetsForm @new-job="onJobCreated" :jobIds="jobIds" />
          <WelcomeBox v-if="showWelcome" @dismiss="onWelcomeDismissed"/>

            <JobDisplay v-for="jobId in jobIds"
                        :job="jobs[jobId]"
                        @dismiss-job="onJobDismissed" />

              Created by Johannes Baiter

`,
  methods: {
    onWelcomeDismissed: function() {
      localStorage.setItem('hideWelcome', true);
      this.showWelcome = false;
    },
    onJobCreated: function(job) {
      this.jobIds.push(job.id);
      this.$set(this.jobs, job.id, job);
      var vm = this;
      var eventStream = new EventSource("/api/tasks/" + job.id + "/stream");
      eventStream.addEventListener('message', function(event) {
        vm.$set(vm.jobs, job.id, JSON.parse(event.data));
        if (event.data.status === 'finished' || event.data.status === 'failed') {
          eventStream.close();
        }
      });
      this.$set(this.streams, job.id, eventStream);
    },
    onJobDismissed: function(jobId) {
      this.jobIds.splice(this.jobIds.indexOf(jobId), 1);
      this.$delete(this.jobs, jobId);
    }
  }
});

app.$mount(".app");
<commit_msg>Fix request  body for email notifications<commit_after>/* global Vue */

// ChildNode.remove() polyfill for Internet Explorer
// from: https://github.com/jserz/js_piece/blob/master/DOM/ChildNode/remove()/remove().md
(function (arr) {
  arr.forEach(function (item) {
    item.remove = item.remove || function () {
      this.parentNode.removeChild(this);
    };
  });
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);

Vue.component("WelcomeBox", {
  template: `

Hi there!

        <button @click="onDismiss" class="delete"></button>

          This application allows you to create fresh new
          IIIF
          manifests from
          your dusty old
          METS/MODS files.
          Having IIIF manifests opens up a lot of possibilities:

You can now create and share annotations on your digitized works
              (they are stored on our server, but they can be used from anywhere)

Since IIIF is Linked Data all the way down, you get free permalinks
              to all parts of the object

With Mirador, you can compare objects from multiple sources
              side-by-side in the same window

          The application is currently a work-in-progress and has only been
          tested with METS/MODS files that follow the recommendations of
          the German Research Foundation (DFG). If you want to give it a spin,
          pick an object from the Central Directory of
          Digitized Prints and put the URL to the METS in the input above.

          Since the venerable DFG-Viewer
          is based on METS, you can also put a DFG-Viewer URL into the input box
          and the application will extract the URL of the corresponding METS
          document by itself.

`,
  methods: {
    onDismiss: function() {
      this.$emit('dismiss');
    }
  }
});

Vue.component("JobDisplay", {
  props: ['job'],
  data: function() {
    return {
      queueLength: this.job.position + 1
    };
  },
  template: `

      <ErrorDisplay v-if="job.status === 'failed'"
                    :traceback="job.traceback"
                    @dismiss="triggerClose" />

{{ job.label || job.metsurl }}

                {{ job.status }}

                <progress class="progress" :value="completionRatio" max="1">
                  {{ job.status }}
                </progress>

              <input @click="onMetsUrlClick" :value="job.result" type="url"
                     class="input metsurl" ref="metsurl" readonly>

                Open in viewer

`,
  computed: {
    showProgressBar: function() {
      return (this.job.status === 'queued' || this.job.status === 'started');
    },
    completionRatio: function() {
      if (this.job.status === 'queued') {
        return ((this.queueLength + 1) - (this.job.position + 1)) / (this.queueLength + 1);
      } else if (this.job.status === 'started') {
        return this.job.current_image / this.job.total_images;
      } else {
        return null;
      }
    },
    viewerUrl: function() {
      if (this.job.result) {
        var manifestId = this.job.result.replace(/.*?\/iiif\/(.*?)\/manifest/, '$1')
        return `/view/${manifestId}`;
      }
    }
  },
  methods: {
    triggerClose: function() {
      this.$emit('dismiss-job', this.job.id);
    },
    onMetsUrlClick: function() {
      this.$refs.metsurl.setSelectionRange(0, this.$refs.metsurl.value.length);
    }
  }
});

Vue.component("NotificationForm", {
  props: ['jobIds'],
  data: function() {
    return {
      viewForm: false,
      recipient: '',
      wasSubmitted: false,
      errorMessage: null,
      invalid: false,
      isLoading: false
    };
  },
  template: `

          <input v-model="viewForm" type="checkbox"> Notify me via email

        <input v-model="recipient" :class="{'is-danger': isDisabled}"
               @invalid="invalidate" @click="removeError"
               class="input" type="email" placeholder="Enter your email">
        <button @click="registerForNotifications" :disabled="isDisabled"
                :class="{'is-loading': isLoading}"
                type="submit" class="button">Submit</button>
        {{ errorMessage }}\

        <button @click="dismiss" class="delete"></button>
        You will be notified at {{ recipient }} once the manifests are finished

`,
  computed: {
    isDisabled: function() {
      return this.invalid || this.errorMessage !== null;
    }
  },
  methods: {
    invalidate: function() {
      this.invalid = true;
    },
    removeError: function() {
      this.errorMessage = null;
      this.invalid = false;
    },
    registerForNotifications: function() {
      var vm = this;
      vm.isLoading = true;
      axios.post('/api/tasks/notify', {recipient: this.recipient,
                                       jobs: this.jobIds})
        .then(function(resp) {
          vm.wasSubmitted = true;
          vm.$emit('subscribe-to-notifications', vm.recipient);
          vm.isLoading = false;
        })
        .catch(function(err) {
          if (err.response) {
            vm.errorMessage = err.response.data.message;
          } else {
            console.error(err);
          }
          vm.isLoading = false;
        });
    },
    dismiss: function() {
      this.$emit('dismiss-notification');
    }
  }
});

Vue.component("ErrorDisplay", {
  props: ['metsUrl', 'traceback'],
  data: function() {
    return {
      showTraceback: false,
      url: this.metsUrl
    };
  },
  template: `

Could not import METS

        <button @click="onDismiss" class="delete"></button>

            Unfortunately we were unable to generate a IIIF manifest from the METS
            located at

            {{ url }}.

            The error was logged in our backend and will be examined. If you wish
            to help with debugging, you can consult the traceback below and open
            an issue on GitHub.

          <button class="button is-danger" @click="toggleTraceback">Traceback</button>

{{ traceback }}

`,
  methods: {
    onDismiss: function() {
      this.$emit('dismiss');
    },
    toggleTraceback: function() {
      this.showTraceback = !this.showTraceback;
    }
  }
});

Vue.component("MetsForm", {
  props: ['jobIds'],
  data: function() {
    return {
      metsUrl: '',
      errorMessage: null,
      traceback: null,
      showTraceback: false,
      invalid: false,
      showNotificationForm: true,
      subscribedToNotifications: false,
      isLoading: false
    };
  },
  template: `

      <NotificationForm v-if="showNotificationForm && hasJobs" :jobIds="jobIds"
                        @subscribe-to-notifications="onSubscribeToNotifications"
                        @dismiss-notification="onDismissNotification"/>
      <form @submit.prevent>

          <input v-model="metsUrl" type="url" class="input is-large"
                  @click="removeError" name="metsUrl" @invalid="invalidate"
                  placeholder="Put a METS (or DFG-Viewer) URL in here!"
                  :class="{'is-danger': isDisabled}">
          <button @click="submitUrl" class="button is-primary is-large iiif-btn"
                  type="submit" :disabled="isDisabled"
                  :class="{'is-disabled': isDisabled, 'is-loading': isLoading}">

          </button>

        {{ errorMessage }}
      </form>
      <ErrorDisplay v-if="!errorMessage && traceback" @dismiss="onDismissError"
                    :metsUrl="metsUrl" :traceback="traceback" />

`,
  computed: {
    isDisabled: function() {
      return this.invalid || this.errorMessage !== null;
    },
    hasJobs: function() {
      return this.jobIds.length > 0;
    }
  },
  methods: {
    invalidate: function() {
      this.invalid = true;
    },
    removeError: function() {
      this.invalid = false;
      this.errorMessage = null;
    },
    onDismissError: function() {
      this.invalid = false;
      this.errorMessage = null;
      this.traceback = null;
    },
    submitUrl: function() {
      var vm = this;
      vm.isLoading = true;
      axios.post('/api/import', {url: this.metsUrl})
        .then(function(resp) {
          vm.errorMessage = null;
          vm.metsUrl = '';
          vm.$emit("new-job", resp.data);
          if (vm.subscribedToNotifications) {
            axios.post('/api/tasks/notify', {recipient: vm.subscriptionAddress,
                                             jobs: [resp.data.id]});
          }
          vm.isLoading = false;
        })
        .catch(function(err) {
          if (err.response && err.response.data.message) {
            vm.errorMessage = err.response.data.message;
          } else if (err.response && err.response.data.traceback) {
            vm.traceback = err.response.data.traceback;
          } else {
            console.error(err);
          }
          vm.isLoading = false;
        });
    },
    onDismissNotification: function() {
      this.showNotificationForm = false;
    },
    onSubscribeToNotifications: function(recipient) {
      this.subscribedToNotifications = true;
      this.subscriptionAddress = recipient;
    }
  }
});

var app = new Vue({
  data: {
    jobIds: [],  // to store the order the jobs were added in
    jobs: {},
    streams: {},
    showWelcome: !localStorage.getItem('hideWelcome')
  },
  template: `

              demetsiiify

          <input type="checkbox" class="is-hidden" id="nav-toggle-state" />

            Recent Imports
            API
            About

          <MetsForm @new-job="onJobCreated" :jobIds="jobIds" />
          <WelcomeBox v-if="showWelcome" @dismiss="onWelcomeDismissed"/>

            <JobDisplay v-for="jobId in jobIds"
                        :job="jobs[jobId]"
                        @dismiss-job="onJobDismissed" />

              Created by Johannes Baiter

`,
  methods: {
    onWelcomeDismissed: function() {
      localStorage.setItem('hideWelcome', true);
      this.showWelcome = false;
    },
    onJobCreated: function(job) {
      this.jobIds.push(job.id);
      this.$set(this.jobs, job.id, job);
      var vm = this;
      var eventStream = new EventSource("/api/tasks/" + job.id + "/stream");
      eventStream.addEventListener('message', function(event) {
        vm.$set(vm.jobs, job.id, JSON.parse(event.data));
        if (event.data.status === 'finished' || event.data.status === 'failed') {
          eventStream.close();
        }
      });
      this.$set(this.streams, job.id, eventStream);
    },
    onJobDismissed: function(jobId) {
      this.jobIds.splice(this.jobIds.indexOf(jobId), 1);
      this.$delete(this.jobs, jobId);
    }
  }
});

app.$mount(".app");
<|endoftext|>


<commit_before>var _const = require("@/common/const");
var profile = require("@/app/tools/profile");
var eventListener = require("@/app/tools/eventListener");
var apiHelper = require("../apis");
var channel = require("../channel");
var commonConfig = require("@/common/config");

module.exports = function(){
	eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _getGreetings);
	eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _getGreetings);
};

function _getGreetings(officialAccount){
	if(officialAccount !== profile.systemOfficialAccount) return;
	if(officialAccount.isSessionOpen) return;
	Promise.all([
		apiHelper.getSystemGreeting(),
		apiHelper.getRobertGreeting(),
		apiHelper.getSkillgroupMenu(),
	]).then(function(result){
		var systemGreetingText = result[0];
		var robotGreetingObj = result[1];
		var groupMenus = result[2];

		var greetingTextType = robotGreetingObj.greetingTextType;
		var greetingText = robotGreetingObj.greetingText;
		var laiye = robotGreetingObj.laiye;
		var greetingObj = {};

		// 系统欢迎语
		systemGreetingText && channel.handleMessage({
			data: systemGreetingText,
		}, {
			type: "txt",
			noPrompt: true
		});
		// 机器人欢迎语
		switch(greetingTextType){
		case 0:
			// 文本消息
			channel.handleMessage({
				data: greetingText,
			}, {
				type: "txt",
				noPrompt: true,
				laiye: laiye
			});
			break;
		case 1:
			// 菜单消息
			// 适配后端有转义两次／三次的情况
			greetingObj = JSON.parse(greetingText.replace(/&amp;quot;|&quot;/g, "\""));
			greetingObj.ext && channel.handleMessage({
				ext: greetingObj.ext,
			}, {
				type: "txt",
				noPrompt: true,
				laiye: laiye
			});
			break;
		case 2:
			// 图片消息
			// 适配后端有转义两次／三次的情况
			greetingObj = JSON.parse(greetingText.replace(/&amp;quot;|&quot;/g, "\""));
			greetingObj.url = commonConfig.getConfig().domain + greetingObj.urlPath;
			channel.handleMessage(greetingObj, { type: "img", noPrompt: true, laiye: laiye });
			break;
		case undefined:
			// 未设置机器人欢迎语
			break;
		default:
			console.error("unknown robot greeting type.");
			break;
		}

		// 技能组列表（机器人菜单）
		if(groupMenus){
			channel.handleMessage({
				data: groupMenus,
			}, {
				type: "skillgroupMenu",
				noPrompt: true
			});
		}

		// 询前引导
		if(!greetingTextType && !greetingText && profile.grayList.transfermanualmenuguide && profile.isManualMenuGuide){
			apiHelper.getTransferManualMenu().then(function(result){
				channel.handleMessage({
					data: result,
				}, {
					type: "transferManualMenu",
					noPrompt: true
				});
			});
		}

	});
}
<commit_msg>CLOUD-18421 前端 - 欢迎语和会话标签里如果加特殊符号，显示异常(欢迎语)<commit_after>var _const = require("@/common/const");
var profile = require("@/app/tools/profile");
var eventListener = require("@/app/tools/eventListener");
var apiHelper = require("../apis");
var channel = require("../channel");
var commonConfig = require("@/common/config");

module.exports = function(){
	eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _getGreetings);
	eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _getGreetings);
};

function _getGreetings(officialAccount){
	if(officialAccount !== profile.systemOfficialAccount) return;
	if(officialAccount.isSessionOpen) return;
	Promise.all([
		apiHelper.getSystemGreeting(),
		apiHelper.getRobertGreeting(),
		apiHelper.getSkillgroupMenu(),
	]).then(function(result){
		var systemGreetingText = result[0];
		var robotGreetingObj = result[1];
		var groupMenus = result[2];

		var greetingTextType = robotGreetingObj.greetingTextType;
		var greetingText = robotGreetingObj.greetingText;
		var laiye = robotGreetingObj.laiye;
		var greetingObj = {};

		// 系统欢迎语
		systemGreetingText && channel.handleMessage({
			data: systemGreetingText,
		}, {
			type: "txt",
			noPrompt: true
		});
		// 机器人欢迎语
		switch(greetingTextType){
		case 0:
			// 文本消息
			// 适配后端有转义两次／三次的情况
			greetingText = greetingText.replace(/&amp;amp;#39;|&amp;#39;|＆amp;#39;/g, "'");
			channel.handleMessage({
				data: greetingText,
			}, {
				type: "txt",
				noPrompt: true,
				laiye: laiye
			});
			break;
		case 1:
			// 菜单消息
			// 适配后端有转义两次／三次的情况
			greetingText = greetingText.replace(/&amp;amp;#39;|&amp;#39;|＆amp;#39;/g, "'");
			greetingObj = JSON.parse(greetingText.replace(/&amp;quot;|&quot;/g, "\""));

			greetingObj.ext && channel.handleMessage({
				ext: greetingObj.ext,
			}, {
				type: "txt",
				noPrompt: true,
				laiye: laiye
			});
			break;
		case 2:
			// 图片消息
			// 适配后端有转义两次／三次的情况
			greetingObj = JSON.parse(greetingText.replace(/&amp;quot;|&quot;/g, "\""));
			greetingObj.url = commonConfig.getConfig().domain + greetingObj.urlPath;
			channel.handleMessage(greetingObj, { type: "img", noPrompt: true, laiye: laiye });
			break;
		case undefined:
			// 未设置机器人欢迎语
			break;
		default:
			console.error("unknown robot greeting type.");
			break;
		}

		// 技能组列表（机器人菜单）
		if(groupMenus){
			channel.handleMessage({
				data: groupMenus,
			}, {
				type: "skillgroupMenu",
				noPrompt: true
			});
		}

		// 询前引导
		if(!greetingTextType && !greetingText && profile.grayList.transfermanualmenuguide && profile.isManualMenuGuide){
			apiHelper.getTransferManualMenu().then(function(result){
				channel.handleMessage({
					data: result,
				}, {
					type: "transferManualMenu",
					noPrompt: true
				});
			});
		}

	});
}
<|endoftext|>


<commit_before>fdescribe('walletHistoryService', function(){
  var history = [];
  var historyStringFull;
  var storageServiceMock;
  var txFormatServiceMock;
  var walletMock;
  var walletHistoryService;

  beforeEach(function(){
    module('ngLodash');
    module('bitcoincom.services');

    storageServiceMock = jasmine.createSpyObj(['getTxHistory', 'removeTxHistory', 'setTxHistory']);
    txFormatServiceMock = jasmine.createSpyObj(['processTx']);
    txFormatServiceMock.processTx.and.callFake(function(coin, tx){
      return tx;
    });
    walletMock = jasmine.createSpyObj(['getTxHistory']);

    module(function($provide) {
      $provide.value('storageService', storageServiceMock);
      $provide.value('txFormatService', txFormatServiceMock);
    });

    inject(function($injector){
      walletHistoryService = $injector.get('walletHistoryService');
    });

    for(var i = 0; i < 100; i++) {
      history.push({
        confirmations: i,
        time: (Date.now() / 1000) - i,
        txid: 'id' + i.toString()
      });
    }
    historyStringFull = JSON.stringify(history);
  });

  it('getCachedHistory empty', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, "[]");
    });

    walletHistoryService.getCachedTxHistory('wallet1234', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet1234');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(0);
  });

  it('getCachedHistory error from storage', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(new Error('Something went wrong.'), null);
    });

    walletHistoryService.getCachedTxHistory('wallet12345', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet12345');
    expect(returnedErr.message).toBe('Something went wrong.');
    expect(returnedHistory.length).toBe(0);
  });

  it('getCachedHistory page full', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, JSON.stringify(history.slice(0, 50)));
    });

    walletHistoryService.getCachedTxHistory('wallet1234', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet1234');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);
  });

  it('updateLocalTxHistoryByPage, getEarlier, keep cache, sufficient overlap so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet67890';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, JSON.stringify(history.slice(0, 40)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, false, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet67890');
    expect(walletIdForStorageSet).toBe('wallet67890');
    expect(fetchSkip).toBe(30);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(80);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(80);
  });

  it('updateLocalTxHistoryByPage, cache empty, getLatest, do not flush cache, some new so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, "[]");
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, 10));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(10);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(10);
  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, nothing new so nothing added.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var walletIdForStorageGet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(0, 40)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, []);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(40);
    expect(storageServiceMock.setTxHistory.calls.any()).toBe(false);
  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, confirmations increased, saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(2, 52)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      var historyWithHigherConfirmations = [];
      for (var i = 0; i < 50; i++) {
        historyWithHigherConfirmations.push({
          confirmations: i >= 6 ? history[i].confirmations : history[i].confirmations + 1,
          time: history[i].time,
          txid: history[i].txid
        });
      }

      cb(null, historyWithHigherConfirmations.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(52);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(52);
    expect(returnedHistory[2].confirmations).toBe(3);

  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, some new with insufficient overlap, so only new saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(48, 78)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);

  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, some new with sufficient overlap so all saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(42, 72)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(72);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(72);

  });

  it('updateLocalTxHistoryByPage, getLatest, flush cache, some new so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageSet;
    walletMock.id = 'wallet7890';

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, true, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageSet).toBe('wallet7890');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);
    expect(storageServiceMock.getTxHistory.calls.any()).toBe(false);
  });

});<commit_msg>Unit test case for 2 items in cache and that being all of the transactions.<commit_after>fdescribe('walletHistoryService', function(){
  var history = [];
  var historyStringFull;
  var storageServiceMock;
  var txFormatServiceMock;
  var walletMock;
  var walletHistoryService;

  beforeEach(function(){
    module('ngLodash');
    module('bitcoincom.services');

    storageServiceMock = jasmine.createSpyObj(['getTxHistory', 'removeTxHistory', 'setTxHistory']);
    txFormatServiceMock = jasmine.createSpyObj(['processTx']);
    txFormatServiceMock.processTx.and.callFake(function(coin, tx){
      return tx;
    });
    walletMock = jasmine.createSpyObj(['getTxHistory']);

    module(function($provide) {
      $provide.value('storageService', storageServiceMock);
      $provide.value('txFormatService', txFormatServiceMock);
    });

    inject(function($injector){
      walletHistoryService = $injector.get('walletHistoryService');
    });

    for(var i = 0; i < 100; i++) {
      history.push({
        confirmations: i,
        time: (Date.now() / 1000) - i,
        txid: 'id' + i.toString()
      });
    }
    historyStringFull = JSON.stringify(history);
  });

  it('getCachedHistory empty', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, "[]");
    });

    walletHistoryService.getCachedTxHistory('wallet1234', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet1234');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(0);
  });

  it('getCachedHistory error from storage', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(new Error('Something went wrong.'), null);
    });

    walletHistoryService.getCachedTxHistory('wallet12345', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet12345');
    expect(returnedErr.message).toBe('Something went wrong.');
    expect(returnedHistory.length).toBe(0);
  });

  it('getCachedHistory page full', function() {
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet = '';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, JSON.stringify(history.slice(0, 50)));
    });

    walletHistoryService.getCachedTxHistory('wallet1234', function(err, txHistory){
      returnedErr = err;
      returnedHistory = txHistory;
    });

    expect(walletIdForStorageGet).toBe('wallet1234');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);
  });

  it('updateLocalTxHistoryByPage, 2 in cache, getEarlier, keep cache, same 2 returned, so all transactions received.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedAllFetched;
    var returnedErr;
    var returnedHistory;
    var walletIdForStorageGet;
    walletMock.id = 'wallet456';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, JSON.stringify(history.slice(0, 2)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, 2));
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, false, false, function(err, txs, allFetched){
      returnedErr = err;
      returnedHistory = txs;
      returnedAllFetched = allFetched;
    });

    expect(walletIdForStorageGet).toBe('wallet456');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(2);
    expect(returnedAllFetched).toBe(true);
    expect(storageServiceMock.setTxHistory.calls.any()).toBe(false);
  });

  it('updateLocalTxHistoryByPage, getEarlier, keep cache, sufficient overlap so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedErr;
    var returnedHistory;
    var returnedAllFetched;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet67890';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;

      cb(null, JSON.stringify(history.slice(0, 40)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, false, false, function(err, txs, allFetched){
      returnedErr = err;
      returnedHistory = txs;
      returnedAllFetched = allFetched;
    });

    expect(walletIdForStorageGet).toBe('wallet67890');
    expect(walletIdForStorageSet).toBe('wallet67890');
    expect(fetchSkip).toBe(30);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(80);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(80);
    expect(returnedAllFetched).toBe(false);
  });

  it('updateLocalTxHistoryByPage, cache empty, getLatest, do not flush cache, one new so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, "[]");
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, 1));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(1);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(1);
  });

  it('updateLocalTxHistoryByPage, cache empty, getLatest, do not flush cache, some new so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, "[]");
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, 10));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(10);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(10);
  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, nothing new so nothing added.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var walletIdForStorageGet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(0, 40)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, []);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(40);
    expect(storageServiceMock.setTxHistory.calls.any()).toBe(false);
  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, confirmations increased, saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(2, 52)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      var historyWithHigherConfirmations = [];
      for (var i = 0; i < 50; i++) {
        historyWithHigherConfirmations.push({
          confirmations: i >= 6 ? history[i].confirmations : history[i].confirmations + 1,
          time: history[i].time,
          txid: history[i].txid
        });
      }

      cb(null, historyWithHigherConfirmations.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(52);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(52);
    expect(returnedHistory[2].confirmations).toBe(3);

  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, some new with insufficient overlap, so only new saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(48, 78)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);

  });

  it('updateLocalTxHistoryByPage, some cachedTx, getLatest, do not flush cache, some new with sufficient overlap so all saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageGet;
    var walletIdForStorageSet;
    walletMock.id = 'wallet789';

    storageServiceMock.getTxHistory.and.callFake(function(walletId, cb){
      walletIdForStorageGet = walletId;
      cb(null, JSON.stringify(history.slice(42, 72)));
    });

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(fetchSkip, fetchSkip + fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, false, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageGet).toBe('wallet789');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(walletIdForStorageSet).toBe('wallet789');
    expect(savedTxs.length).toBe(72);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(72);

  });

  it('updateLocalTxHistoryByPage, getLatest, flush cache, some new so saved.', function(){
    var fetchLimit;
    var fetchSkip;
    var returnedHistory;
    var savedTxs;
    var walletIdForStorageSet;
    walletMock.id = 'wallet7890';

    walletMock.getTxHistory.and.callFake(function(opts, cb){
      fetchSkip = opts.skip;
      fetchLimit = opts.limit;

      cb(null, history.slice(0, fetchLimit));
    });

    storageServiceMock.setTxHistory.and.callFake(function(txs, walletId, cb){
      savedTxs = txs;
      walletIdForStorageSet = walletId;
      cb(null);
    });

    walletHistoryService.updateLocalTxHistoryByPage(walletMock, true, true, function(err, txs){
      returnedErr = err;
      returnedHistory = txs;
    });

    expect(walletIdForStorageSet).toBe('wallet7890');
    expect(fetchSkip).toBe(0);
    expect(fetchLimit).toBe(50);
    expect(savedTxs.length).toBe(50);
    expect(returnedErr).toBeNull();
    expect(returnedHistory.length).toBe(50);
    expect(storageServiceMock.getTxHistory.calls.any()).toBe(false);
  });

});<|endoftext|>


<commit_before>var lastAlternatingLine = false;

function applyQuirks(text) {

	// Global quirking tags

		text = text.replace(/\[[cC](?:[aA][pP][sS])?\](.*?)\[\/[cC](?:[aA][pP][sS])?\]/g, '¦¤¤¤¤¦ $1 ¦¤¤¤¦¦');
		text = text.replace(/\[[wW](?:[hH][iI][sS][pP][eE][rR])?\](.*?)\[\/[wW](?:[hH][iI][sS][pP][eE][rR])?\]/g, '¦¤¤¤¦ $1 ¦¤¤¦¦');

	// Case
	switch (user.character['case']) {
		case "lower":
			text = text.replace(/([A-Z][a-z]+\b)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/(\b)([A-Z]'?[A-Z]+)(\b)/g, '¥$2¥');
			text = text.toLowerCase();
			text = text.replace(/¥([\w|']+)¥/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/¥/g, '');
			text = text.replace(/([A-Z]\W[a-z]\W[A-Z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/^([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/[\.|!|\?]\W([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/([A-Z]'[a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "upper":
			text = text.toUpperCase();
			text = text.replace(/¦¤¤¤¦(.*?)¦¤¤¦¦/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			break;
		case "title":
			text = text.replace(/(^[a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/([\s|-][a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "inverted":
	        text = text.replace(/(?:^|¦¤¤¦¦)(.*?)(?:$|¦¤¤¤¦)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/^(\w)/, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/([iI])\b/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/([,\.]\s?\w)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			break;
		case "alternating":
	        text = text.toLowerCase();
			text = text.replace(/([\w\s]|[\w'\w])([\w'\w]|[\w\s])?/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "alt-lines":
			if (lastAlternatingLine) {
				text = text.toUpperCase();
			} else {
				text = text.toLowerCase();
			}
			break;
		case "proper":
			text = text.replace(/^(\w)/, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/(?:[^\.]|^[^\.])[!|\?|\.](\s\w)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/[\s|^](i)['|\W|$]/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "first-caps":
			text = text.replace(/([A-Z][a-z]+\b)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/(\b)([A-Z]'?[A-Z]+)(\b)/g, '¥$2¥');
			text = text.toLowerCase();
			text = text.replace(/¥([\w|']+)¥/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/¥/g, '');
			text = text.replace(/(?:^|\*)(\w)/, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/(?:[^\.]|^[^\.])[!|\?|\.](\s\w)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;

	}

	// Replacements

	for (var i=0; i < user.character.replacements.length; i++) {
		var replacement = user.character.replacements[i];
		if (replacement[1] == " ") {
		replacement[1] = ""
		}
		if (replacement[0].match(/\/.*?\//)) {
		str1 = replacement[0]
		str1 = str1.replace(/^\/(.*?)\//g, '$1');
		str2 = replacement[1]
		try {
		   var re = new RegExp(str1, "g");
		}
		catch (e) {console.log("A young person stands in their bedroom. They don't know Regexp.")}
		if (str2 == "$L")
				{
					try {
					text = text.replace(re, function(a,x){ return a.replace(x,x.toLowerCase()); });
					}
					catch (e) {
					text = text.replace(re, function(a){ return a.replace(a,a.toLowerCase()); });
					}
				}
		else if (str2 == "$U")
				{
					try {
					text = text.replace(re, function(a,x){ return a.replace(x,x.toUpperCase()); });
					}
					catch (e) {
					text = text.replace(re, function(a){ return a.replace(a,a.toUpperCase()); });
					}
				}
		else {
			  text = text.replace(re, str2);
		}}
		else {
		 RegExp.quote = function(str) {
			 return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
		 };
		 str1 = replacement[0]
		  str2 = replacement[1]
		var re = new RegExp(RegExp.quote(str1), "g");
		text = text.replace(re, str2);
		}
	}

	// Cleanup global quirking tags

	text = text.replace(/¦¤¤¤¤¦(.*?)¦¤¤¤¦¦/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
	text = text.replace(/(¦¤¤¤¤?¦\s)/g, '');
	text = text.replace(/(\s¦¤¤¤?¦¦)/g, '');

	// Prefix
	if (user.character.quirk_prefix!='' || user.character.quirk_suffix!='') {
		text = user.character.quirk_prefix+' '+text+' '+user.character.quirk_suffix;
	}

	return text

}

function depunct(txt) {
	return txt.replace(/[.,?!']/g, '');
}

<commit_msg>More adaptive Jake quirk<commit_after>var lastAlternatingLine = false;

function applyQuirks(text) {

	// Global quirking tags

		text = text.replace(/\[[cC](?:[aA][pP][sS])?\](.*?)\[\/[cC](?:[aA][pP][sS])?\]/g, '¦¤¤¤¤¦ $1 ¦¤¤¤¦¦');
		text = text.replace(/\[[wW](?:[hH][iI][sS][pP][eE][rR])?\](.*?)\[\/[wW](?:[hH][iI][sS][pP][eE][rR])?\]/g, '¦¤¤¤¦ $1 ¦¤¤¦¦');

	// Case
	switch (user.character['case']) {
		case "lower":
			text = text.replace(/([A-Z][a-z]+\b)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/(\b)([A-Z]'?[A-Z]+)(\b)/g, '¥$2¥');
			text = text.toLowerCase();
			text = text.replace(/¥([\w|']+)¥/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/¥/g, '');
			text = text.replace(/([A-Z]\W[a-z]\W[A-Z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/^([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/[\.|!|\?]\W([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/([A-Z]'[a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "upper":
			text = text.toUpperCase();
			text = text.replace(/¦¤¤¤¦(.*?)¦¤¤¦¦/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			break;
		case "title":
			text = text.replace(/(^[a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/([\s|-][a-z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "inverted":
	        text = text.replace(/(?:^|¦¤¤¦¦)(.*?)(?:$|¦¤¤¤¦)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/^(\w)/, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/([iI])\b/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/([,\.]\s?\w)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			break;
		case "alternating":
	        text = text.toLowerCase();
			text = text.replace(/([\w\s]|[\w'\w])([\w'\w]|[\w\s])?/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "alt-lines":
			if (lastAlternatingLine) {
				text = text.toUpperCase();
			} else {
				text = text.toLowerCase();
			}
			break;
		case "proper":
			text = text.replace(/^(\w)/, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/(?:[^\.]|^[^\.])[!|\?|\.](\s\w)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/[\s|^](i)['|\W|$]/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;
		case "first-caps":
			text = text.replace(/([A-Z][a-z]+\b)/g, function(a,x){ return a.replace(x,x.toLowerCase()); });
			text = text.replace(/(\b)([A-Z]'?[A-Z]+)(\b)/g, '¥$2¥');
			text = text.toLowerCase();
			text = text.replace(/¥([\w|']+)¥/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/¥/g, '');
			text = text.replace(/([A-Z]\W[a-z]\W[A-Z])/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/^([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/[\.|!|\?]\W([a-z]\W[A-Z][A-Z]+)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/(?:^|\*)(\w)/, function(a,x){ return a.replace(x,x.toUpperCase()); });
			text = text.replace(/(?:[^\.]|^[^\.])[!|\?|\.](\s\w)/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
			break;

	}

	// Replacements

	for (var i=0; i < user.character.replacements.length; i++) {
		var replacement = user.character.replacements[i];
		if (replacement[1] == " ") {
		replacement[1] = ""
		}
		if (replacement[0].match(/\/.*?\//)) {
		str1 = replacement[0]
		str1 = str1.replace(/^\/(.*?)\//g, '$1');
		str2 = replacement[1]
		try {
		   var re = new RegExp(str1, "g");
		}
		catch (e) {console.log("A young person stands in their bedroom. They don't know Regexp.")}
		if (str2 == "$L")
				{
					try {
					text = text.replace(re, function(a,x){ return a.replace(x,x.toLowerCase()); });
					}
					catch (e) {
					text = text.replace(re, function(a){ return a.replace(a,a.toLowerCase()); });
					}
				}
		else if (str2 == "$U")
				{
					try {
					text = text.replace(re, function(a,x){ return a.replace(x,x.toUpperCase()); });
					}
					catch (e) {
					text = text.replace(re, function(a){ return a.replace(a,a.toUpperCase()); });
					}
				}
		else {
			  text = text.replace(re, str2);
		}}
		else {
		 RegExp.quote = function(str) {
			 return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
		 };
		 str1 = replacement[0]
		  str2 = replacement[1]
		var re = new RegExp(RegExp.quote(str1), "g");
		text = text.replace(re, str2);
		}
	}

	// Cleanup global quirking tags

	text = text.replace(/¦¤¤¤¤¦(.*?)¦¤¤¤¦¦/g, function(a,x){ return a.replace(x,x.toUpperCase()); });
	text = text.replace(/(¦¤¤¤¤?¦\s)/g, '');
	text = text.replace(/(\s¦¤¤¤?¦¦)/g, '');

	// Prefix
	if (user.character.quirk_prefix!='' || user.character.quirk_suffix!='') {
		text = user.character.quirk_prefix+' '+text+' '+user.character.quirk_suffix;
	}

	return text

}

function depunct(txt) {
	return txt.replace(/[.,?!']/g, '');
}

<|endoftext|>


<commit_before>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {
            console.log('in getShelters success');
            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          var options;

          if (shelters.result[i].HasMeals === "Y") {
            options = {icon: '/img/food.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsShelter === "Y") {
            options = {icon: '/img/shelter.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsShelter === "Y" && shelters.result[i].HasMeals === "Y") {
            options = {icon: '/img/shelter_food.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsIntake === "Y") {
            options = {icon: '/img/intake.png', shadow: markerShadow};
          }

          _self.createMarker(latlng, shelters.result[i].Name + "
" + shelters.result[i].Address1, options);
        }
      },

      createMarker: function (latlng, description, options) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

           _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push(marker);
        } else {
          needMarkerCollection.push(marker);
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        geocoder.geocode({
          'address': addr,
        }, function (results, status) {
          if (status === google.maps.GeocoderStatus.OK) {
            _self.createMarker(results[0].geometry.location, 'Your Location',  {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }
        });
      },

      removeMarkers: function(markerArray) {
        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].setMap(null);
          }
          markerArray.length = 0;
        }
      }

    };

    return _self;
  };
})(jQuery);
<commit_msg>Wired geocoding and fetching of new nearest-sorted need list<commit_after>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {
            console.log('in getShelters success');
            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          var options;

          if (shelters.result[i].HasMeals === "Y") {
            options = {icon: '/img/food.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsShelter === "Y") {
            options = {icon: '/img/shelter.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsShelter === "Y" && shelters.result[i].HasMeals === "Y") {
            options = {icon: '/img/shelter_food.png', shadow: markerShadow};
          }
          if (shelters.result[i].IsIntake === "Y") {
            options = {icon: '/img/intake.png', shadow: markerShadow};
          }

          _self.createMarker(latlng, shelters.result[i].Name + "
" + shelters.result[i].Address1, options);
        }
      },

      createMarker: function (latlng, description, options) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

           _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push(marker);
        } else {
          needMarkerCollection.push(marker);
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        geocoder.geocode({
          'address': addr,
        }, function (results, status) {
          if (status === google.maps.GeocoderStatus.OK) {
            _self.createMarker(results[0].geometry.location, 'Your Location',  {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });

            _self.getShelters(results[0].geometry.location,false);
          }
        });
      },

      removeMarkers: function(markerArray) {
        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].setMap(null);
          }
          markerArray.length = 0;
        }
      }

    };

    return _self;
  };
})(jQuery);
<|endoftext|>


<commit_before>const { CloudEvent } = require("../../../cloudevent");
const ValidationError = require("./validation_error.js");
const {
  sanityAndClone,
  validateArgs
} = require("./commons.js");
const {
  HEADER_CONTENT_TYPE
} = require("../constants");

function check(payload, headers, receiver) {
  validateArgs(payload, headers);

  const sanityHeaders = sanityAndClone(headers);

  // Validation Level 1
  if (!receiver.allowedContentTypes.includes(sanityHeaders[HEADER_CONTENT_TYPE])) {
    throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
  }
  return true;
}

function parse(payload, headers, receiver) {
  check(payload, headers, receiver);

  const sanityHeaders = sanityAndClone(headers);
  const contentType = sanityHeaders[HEADER_CONTENT_TYPE];
  const parser = receiver.parserByMime[contentType];
  const incoming = parser.parse(payload);
  const event = {
    type: undefined,
    source: undefined
  };

  receiver.parserMap.forEach((value, key) => {
    if (incoming[key]) {
      event[value.name] = value.parser(incoming[key]);
      delete incoming[key];
    }
  });

  const cloudevent = new CloudEvent(event);

  // Every unprocessed attribute should be an extension
  Array.from(Object.keys(incoming)).forEach((extension) =>
    cloudevent.addExtension(extension, incoming[extension]));

  cloudevent.spec.check();
  return cloudevent;
}

module.exports = { parse, check };<commit_msg>fix: parse method mutating its input (#231)<commit_after>const { CloudEvent } = require("../../../cloudevent");
const ValidationError = require("./validation_error.js");
const {
  sanityAndClone,
  validateArgs
} = require("./commons.js");
const {
  HEADER_CONTENT_TYPE
} = require("../constants");

function check(payload, headers, receiver) {
  validateArgs(payload, headers);

  const sanityHeaders = sanityAndClone(headers);

  // Validation Level 1
  if (!receiver.allowedContentTypes.includes(sanityHeaders[HEADER_CONTENT_TYPE])) {
    throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
  }
  return true;
}

function parse(payload, headers, receiver) {
  check(payload, headers, receiver);

  const sanityHeaders = sanityAndClone(headers);
  const contentType = sanityHeaders[HEADER_CONTENT_TYPE];
  const parser = receiver.parserByMime[contentType];
  const incoming = { ...parser.parse(payload) };
  const event = {
    type: undefined,
    source: undefined
  };

  receiver.parserMap.forEach((value, key) => {
    if (incoming[key]) {
      event[value.name] = value.parser(incoming[key]);
      delete incoming[key];
    }
  });

  const cloudevent = new CloudEvent(event);

  // Every unprocessed attribute should be an extension
  Array.from(Object.keys(incoming)).forEach((extension) =>
    cloudevent.addExtension(extension, incoming[extension]));

  cloudevent.spec.check();
  return cloudevent;
}

module.exports = { parse, check };<|endoftext|>