<commit_before>import * as _ from "lodash";
import moment from "moment";

export class DashboardService {
    constructor($resource, GLOBALS) {
        return $resource(GLOBALS.BASE_URL + '/api/v1/dashboards', null, {
            query: {
                method: 'GET',
                isArray: false,
                cache: false
            }
        });
    }
}

export class DashboardDataModelAdapter {

    constructor() {
        moment.locale("de");
    }

    compileMyDashboardConfig(dashboardData) {

        let series = [];

        let monthCategories = [];
        for (let i = 0; i < 12; i++) {
            monthCategories[i] = this.convertToMonthLabel(i + 1);
        }

        let licenseStateSeries = [];

        let idx = 0;
        if (dashboardData.GliderPilotFlightStatisticDashboardDetails
            && !_.isEmpty(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings)
            && !_.isEmpty(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours)) {
            series[idx++] = {
                name: "Glider (Anz.)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings),
                color: 'rgba(165,170,217,1)',
                pointPadding: 0.38,
                pointPlacement: -0.3
            };
            series[idx++] = {
                name: "Glider (h)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours),
                color: 'rgba(126,86,134,.9)',
                pointPadding: 0.45,
                pointPlacement: -0.3,
                yAxis: 1
            };

            series[idx++] = {
                name: "Glider (Anz., Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings, true),
                color: 'rgba(165,170,217,.5)',
                pointPadding: 0.38,
                pointPlacement: -0.1
            };
            series[idx++] = {
                name: "Glider (h, Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours, true),
                color: 'rgba(126,86,134,.5)',
                pointPadding: 0.45,
                pointPlacement: -0.1,
                yAxis: 1
            };
        }
        if (dashboardData.MotorPilotFlightStatisticDashboardDetails
            && !_.isEmpty(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours)
            && !_.isEmpty(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings)) {
            series[idx++] = {
                name: "Motor (Anz.)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings),
                color: 'rgba(100,100,100,.8)',
                pointPadding: 0.38,
                pointPlacement: 0.1
            };
            series[idx++] = {
                name: "Motor (h)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours),
                color: 'rgba(70,70,70,1)',
                pointPadding: 0.45,
                pointPlacement: 0.1,
                yAxis: 1
            };

            series[idx++] = {
                name: "Motor (Anz., Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings, true),
                color: 'rgba(100,100,100,.5)',
                pointPadding: 0.38,
                pointPlacement: 0.3
            };
            series[idx++] = {
                name: "Motor (h, Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours, true),
                color: 'rgba(70,70,70,.5)',
                pointPadding: 0.45,
                pointPlacement: 0.3,
                yAxis: 1
            };
        }

        if (dashboardData.GliderLicenceStateDetails) {
            licenseStateSeries[0] = {
                name: 'Benötigt',
                color: 'rgba(70,70,70,.5)',
                data: [
                    dashboardData.GliderLicenceStateDetails.FlightTimeInHoursRequired,
                    dashboardData.GliderLicenceStateDetails.LandingsRequired,
                    dashboardData.GliderLicenceStateDetails.NumberOfCheckFlightsRequired
                ],
                pointPadding: 0.1
            };
            licenseStateSeries[1] = {
                name: 'Erreicht',
                color: 'rgba(126,86,134,.9)',
                data: [
                    dashboardData.GliderLicenceStateDetails.FlightTimeInHours,
                    dashboardData.GliderLicenceStateDetails.Landings,
                    dashboardData.GliderLicenceStateDetails.NumberOfCheckFlights
                ],
                pointPadding: 0.3
            };
        }

        let safety = this.calculateSafetyValues(dashboardData.SafetyDashboardDetails);
        let convertToMonthLabel = this.convertToMonthLabel;

        return {
            person: dashboardData.PersonDashboardDetails || {},
            license: dashboardData.GliderLicenceStateDetails || {},
            safety: safety,
            safetyGauge: {
                options: {
                    chart: {
                        type: 'solidgauge',
                        animation: false
                    },
                    tooltip: {
                        enabled: false
                    },

                    pane: {
                        center: ['50%', '85%'],
                        size: '140%',
                        startAngle: -90,
                        endAngle: 90,
                        background: {
                            backgroundColor: '#EEE',
                            innerRadius: '60%',
                            outerRadius: '100%',
                            shape: 'arc'
                        }
                    }
                },
                series: [{
                    data: [safety.safetyPercentage],
                    dataLabels: {
                        enabled: false
                    }
                }],
                title: {
                    text: ''
                },
                loading: false,
                yAxis: {
                    lineWidth: 0,
                    minorTickInterval: null,
                    tickPixelInterval: 400,
                    tickWidth: 0,
                    min: 0,
                    max: 100,
                    stops: [
                        [0.3, '#DF5353'], // red
                        [0.5, '#DDDF0D'], // yellow
                        [0.8, '#55BF3B'] // green
                    ],
                    labels: {
                        enabled: false
                    }
                }
            },

            flightsChartConfig: {
                options: {
                    chart: {
                        type: 'column',
                        zoomType: 'x'
                    },

                    plotOptions: {
                        column: {
                            grouping: false,
                            shadow: false,
                            borderWidth: 0
                        }
                    },

                    tooltip: {
                        shared: true,
                        useHTML: true,
                        headerFormat: '{point.key}
',
                        pointFormat: '
{series.name}: 	{point.y}
',
                        footerFormat: '
',
                        valueDecimals: 2
                    }
                },
                series: series,
                title: {
                    text: ''
                },
                loading: false,
                xAxis: {
                    categories: monthCategories
                },
                yAxis: [{
                    min: 0,
                    title: {text: 'Anzahl Flüge'},
                    allowDecimals: false
                }, {
                    min: 0,
                    title: {text: 'Flugstunden'},
                    opposite: true,
                    allowDecimals: false
                }]
            },

            licenseStateConfig: {
                options: {
                    chart: {
                        type: 'column'
                    },

                    plotOptions: {
                        column: {
                            grouping: false,
                            shadow: false,
                            borderWidth: 0
                        }
                    },
                    tooltip: {
                        formatter: function () {
                            let required = this.points[0];
                            let done = this.points[1];
                            return `${this.x}:<br><span style="font-weight:500;">${required.series.name}: ${required.y}<span><br><span style="font-weight:500;">${done.series.name}: ${Math.round(done.y * 10) / 10}<span>`;
                        },
                        shared: true
                    }
                },
                series: licenseStateSeries,
                title: {
                    text: ''
                },
                loading: false,
                xAxis: {
                    categories: ['Flugstunden', 'Landungen', 'Checkflüge']
                },
                yAxis: [
                    {
                        min: 0,
                        title: {text: 'Anzahl'},
                        allowDecimals: false,
                        opposite: true
                    }
                ]
            }
        };
    }

    convertToMonthLabel(monthIndex) {
        let month = moment().subtract(1, 'year').add(monthIndex, 'month').format("MMM");
        let prevYear = moment().subtract(2, 'year').add(monthIndex, 'month').format("YY");
        let currentYear = moment().subtract(1, 'year').add(monthIndex, 'month').format("YY");

        return `${month} ${prevYear}/${currentYear}`;
    }

    convertToMonthsArray(flightsByDate, previousYear) {
        let flightsByMonthCurrentYear = [];
        let flightsByMonthPreviousYear = [];

        let twoYearsAgo = moment().subtract(2, 'year');

        for (let i = 0; i < 12; i++) {
            let monthIndex = 0;
            if (previousYear) {
                monthIndex = moment().subtract(1, 'year').month(parseInt(moment().format("M")) - i - 1).format("YYYYMM");
            } else {
                monthIndex = moment().month(parseInt(moment().format("M")) - i - 1).format("YYYYMM");
            }
            flightsByMonthCurrentYear[monthIndex] = 0;
            flightsByMonthPreviousYear[monthIndex] = 0;
        }

        const oneYearAgo = moment().subtract(1, 'year');
        for (let date in flightsByDate) {
            if (flightsByDate.hasOwnProperty(date)) {
                let flightMoment = moment(date);
                let month = flightMoment.format('YYYYMM');
                if (flightMoment.isAfter(twoYearsAgo) && flightMoment.isBefore(oneYearAgo)) {
                    flightsByMonthPreviousYear[month] = (flightsByMonthCurrentYear[month] || 0) + flightsByDate[date];
                } else if (flightMoment.isAfter(oneYearAgo)) {
                    flightsByMonthCurrentYear[month] = (flightsByMonthCurrentYear[month] || 0) + flightsByDate[date];
                }
            }
        }

        if (previousYear) {
            return this.convertMonthsMapToArray(flightsByMonthPreviousYear);
        } else {
            return this.convertMonthsMapToArray(flightsByMonthCurrentYear);
        }
    }

    convertMonthsMapToArray(flightsByMonth) {
        let months = [];
        for (let month in flightsByMonth) {
            if (flightsByMonth.hasOwnProperty(month)) {
                months.push(month);
            }
        }
        months = months.sort();
        let monthlyFlightsArray = [];
        for (let i = 0; i < months.length; i++) {
            let month = months[i];
            monthlyFlightsArray[i] = flightsByMonth[month];
        }
        return monthlyFlightsArray;
    }

    calculateSafetyValues(SafetyDashboardDetails) {
        let flightTimeInHours = SafetyDashboardDetails && SafetyDashboardDetails.FlightTimeInHours || 0;
        let numStarts = SafetyDashboardDetails && SafetyDashboardDetails.Starts || 0;

        let percentageFlightTime = (100 / 30 * flightTimeInHours);
        let percentageNumStarts = (100 / 42 * numStarts);
        let safetyPercentage = (percentageFlightTime + percentageNumStarts) / 2;
        let labelKey = "UNKNOWN";

        if (safetyPercentage > 33 && safetyPercentage < 66) {
            labelKey = "YELLOW";
        } else if (safetyPercentage > 66) {
            labelKey = "GREEN";
        } else {
            labelKey = "RED";
        }

        return {
            safetyPercentage: Math.min(100, Math.round(safetyPercentage * 100) / 100),
            starts: numStarts,
            hours: Math.round(flightTimeInHours),
            labelKey: labelKey
        };
    }
}<commit_msg>#78 raised bar for green color on safety gauge<commit_after>import * as _ from "lodash";
import moment from "moment";

export class DashboardService {
    constructor($resource, GLOBALS) {
        return $resource(GLOBALS.BASE_URL + '/api/v1/dashboards', null, {
            query: {
                method: 'GET',
                isArray: false,
                cache: false
            }
        });
    }
}

export class DashboardDataModelAdapter {

    constructor() {
        moment.locale("de");
    }

    compileMyDashboardConfig(dashboardData) {

        let series = [];

        let monthCategories = [];
        for (let i = 0; i < 12; i++) {
            monthCategories[i] = this.convertToMonthLabel(i + 1);
        }

        let licenseStateSeries = [];

        let idx = 0;
        if (dashboardData.GliderPilotFlightStatisticDashboardDetails
            && !_.isEmpty(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings)
            && !_.isEmpty(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours)) {
            series[idx++] = {
                name: "Glider (Anz.)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings),
                color: 'rgba(165,170,217,1)',
                pointPadding: 0.38,
                pointPlacement: -0.3
            };
            series[idx++] = {
                name: "Glider (h)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours),
                color: 'rgba(126,86,134,.9)',
                pointPadding: 0.45,
                pointPlacement: -0.3,
                yAxis: 1
            };

            series[idx++] = {
                name: "Glider (Anz., Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyLandings, true),
                color: 'rgba(165,170,217,.5)',
                pointPadding: 0.38,
                pointPlacement: -0.1
            };
            series[idx++] = {
                name: "Glider (h, Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.GliderPilotFlightStatisticDashboardDetails.MonthlyFlightHours, true),
                color: 'rgba(126,86,134,.5)',
                pointPadding: 0.45,
                pointPlacement: -0.1,
                yAxis: 1
            };
        }
        if (dashboardData.MotorPilotFlightStatisticDashboardDetails
            && !_.isEmpty(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours)
            && !_.isEmpty(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings)) {
            series[idx++] = {
                name: "Motor (Anz.)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings),
                color: 'rgba(100,100,100,.8)',
                pointPadding: 0.38,
                pointPlacement: 0.1
            };
            series[idx++] = {
                name: "Motor (h)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours),
                color: 'rgba(70,70,70,1)',
                pointPadding: 0.45,
                pointPlacement: 0.1,
                yAxis: 1
            };

            series[idx++] = {
                name: "Motor (Anz., Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyLandings, true),
                color: 'rgba(100,100,100,.5)',
                pointPadding: 0.38,
                pointPlacement: 0.3
            };
            series[idx++] = {
                name: "Motor (h, Vorjahr)",
                data: this.convertToMonthsArray(dashboardData.MotorPilotFlightStatisticDashboardDetails.MonthlyFlightHours, true),
                color: 'rgba(70,70,70,.5)',
                pointPadding: 0.45,
                pointPlacement: 0.3,
                yAxis: 1
            };
        }

        if (dashboardData.GliderLicenceStateDetails) {
            licenseStateSeries[0] = {
                name: 'Benötigt',
                color: 'rgba(70,70,70,.5)',
                data: [
                    dashboardData.GliderLicenceStateDetails.FlightTimeInHoursRequired,
                    dashboardData.GliderLicenceStateDetails.LandingsRequired,
                    dashboardData.GliderLicenceStateDetails.NumberOfCheckFlightsRequired
                ],
                pointPadding: 0.1
            };
            licenseStateSeries[1] = {
                name: 'Erreicht',
                color: 'rgba(126,86,134,.9)',
                data: [
                    dashboardData.GliderLicenceStateDetails.FlightTimeInHours,
                    dashboardData.GliderLicenceStateDetails.Landings,
                    dashboardData.GliderLicenceStateDetails.NumberOfCheckFlights
                ],
                pointPadding: 0.3
            };
        }

        let safety = this.calculateSafetyValues(dashboardData.SafetyDashboardDetails);
        let convertToMonthLabel = this.convertToMonthLabel;

        return {
            person: dashboardData.PersonDashboardDetails || {},
            license: dashboardData.GliderLicenceStateDetails || {},
            safety: safety,
            safetyGauge: {
                options: {
                    chart: {
                        type: 'solidgauge',
                        animation: false
                    },
                    tooltip: {
                        enabled: false
                    },

                    pane: {
                        center: ['50%', '85%'],
                        size: '140%',
                        startAngle: -90,
                        endAngle: 90,
                        background: {
                            backgroundColor: '#EEE',
                            innerRadius: '60%',
                            outerRadius: '100%',
                            shape: 'arc'
                        }
                    }
                },
                series: [{
                    data: [safety.safetyPercentage],
                    dataLabels: {
                        enabled: false
                    }
                }],
                title: {
                    text: ''
                },
                loading: false,
                yAxis: {
                    lineWidth: 0,
                    minorTickInterval: null,
                    tickPixelInterval: 400,
                    tickWidth: 0,
                    min: 0,
                    max: 100,
                    stops: [
                        [0.4, '#DF5353'], // red
                        [0.7, '#DDDF0D'], // yellow
                        [0.9, '#55BF3B'] // green
                    ],
                    labels: {
                        enabled: false
                    }
                }
            },

            flightsChartConfig: {
                options: {
                    chart: {
                        type: 'column',
                        zoomType: 'x'
                    },

                    plotOptions: {
                        column: {
                            grouping: false,
                            shadow: false,
                            borderWidth: 0
                        }
                    },

                    tooltip: {
                        shared: true,
                        useHTML: true,
                        headerFormat: '{point.key}
',
                        pointFormat: '
{series.name}: 	{point.y}
',
                        footerFormat: '
',
                        valueDecimals: 2
                    }
                },
                series: series,
                title: {
                    text: ''
                },
                loading: false,
                xAxis: {
                    categories: monthCategories
                },
                yAxis: [{
                    min: 0,
                    title: {text: 'Anzahl Flüge'},
                    allowDecimals: false
                }, {
                    min: 0,
                    title: {text: 'Flugstunden'},
                    opposite: true,
                    allowDecimals: false
                }]
            },

            licenseStateConfig: {
                options: {
                    chart: {
                        type: 'column'
                    },

                    plotOptions: {
                        column: {
                            grouping: false,
                            shadow: false,
                            borderWidth: 0
                        }
                    },
                    tooltip: {
                        formatter: function () {
                            let required = this.points[0];
                            let done = this.points[1];
                            return `${this.x}:<br><span style="font-weight:500;">${required.series.name}: ${required.y}<span><br><span style="font-weight:500;">${done.series.name}: ${Math.round(done.y * 10) / 10}<span>`;
                        },
                        shared: true
                    }
                },
                series: licenseStateSeries,
                title: {
                    text: ''
                },
                loading: false,
                xAxis: {
                    categories: ['Flugstunden', 'Landungen', 'Checkflüge']
                },
                yAxis: [
                    {
                        min: 0,
                        title: {text: 'Anzahl'},
                        allowDecimals: false,
                        opposite: true
                    }
                ]
            }
        };
    }

    convertToMonthLabel(monthIndex) {
        let month = moment().subtract(1, 'year').add(monthIndex, 'month').format("MMM");
        let prevYear = moment().subtract(2, 'year').add(monthIndex, 'month').format("YY");
        let currentYear = moment().subtract(1, 'year').add(monthIndex, 'month').format("YY");

        return `${month} ${prevYear}/${currentYear}`;
    }

    convertToMonthsArray(flightsByDate, previousYear) {
        let flightsByMonthCurrentYear = [];
        let flightsByMonthPreviousYear = [];

        let twoYearsAgo = moment().subtract(2, 'year');

        for (let i = 0; i < 12; i++) {
            let monthIndex = 0;
            if (previousYear) {
                monthIndex = moment().subtract(1, 'year').month(parseInt(moment().format("M")) - i - 1).format("YYYYMM");
            } else {
                monthIndex = moment().month(parseInt(moment().format("M")) - i - 1).format("YYYYMM");
            }
            flightsByMonthCurrentYear[monthIndex] = 0;
            flightsByMonthPreviousYear[monthIndex] = 0;
        }

        const oneYearAgo = moment().subtract(1, 'year');
        for (let date in flightsByDate) {
            if (flightsByDate.hasOwnProperty(date)) {
                let flightMoment = moment(date);
                let month = flightMoment.format('YYYYMM');
                if (flightMoment.isAfter(twoYearsAgo) && flightMoment.isBefore(oneYearAgo)) {
                    flightsByMonthPreviousYear[month] = (flightsByMonthCurrentYear[month] || 0) + flightsByDate[date];
                } else if (flightMoment.isAfter(oneYearAgo)) {
                    flightsByMonthCurrentYear[month] = (flightsByMonthCurrentYear[month] || 0) + flightsByDate[date];
                }
            }
        }

        if (previousYear) {
            return this.convertMonthsMapToArray(flightsByMonthPreviousYear);
        } else {
            return this.convertMonthsMapToArray(flightsByMonthCurrentYear);
        }
    }

    convertMonthsMapToArray(flightsByMonth) {
        let months = [];
        for (let month in flightsByMonth) {
            if (flightsByMonth.hasOwnProperty(month)) {
                months.push(month);
            }
        }
        months = months.sort();
        let monthlyFlightsArray = [];
        for (let i = 0; i < months.length; i++) {
            let month = months[i];
            monthlyFlightsArray[i] = flightsByMonth[month];
        }
        return monthlyFlightsArray;
    }

    calculateSafetyValues(SafetyDashboardDetails) {
        let flightTimeInHours = SafetyDashboardDetails && SafetyDashboardDetails.FlightTimeInHours || 0;
        let numStarts = SafetyDashboardDetails && SafetyDashboardDetails.Starts || 0;

        let percentageFlightTime = (100 / 30 * flightTimeInHours);
        let percentageNumStarts = (100 / 42 * numStarts);
        let safetyPercentage = (percentageFlightTime + percentageNumStarts) / 2;
        let labelKey = "UNKNOWN";

        if (safetyPercentage > 33 && safetyPercentage < 66) {
            labelKey = "YELLOW";
        } else if (safetyPercentage > 66) {
            labelKey = "GREEN";
        } else {
            labelKey = "RED";
        }

        return {
            safetyPercentage: Math.min(100, Math.round(safetyPercentage * 100) / 100),
            starts: numStarts,
            hours: Math.round(flightTimeInHours),
            labelKey: labelKey
        };
    }
}<|endoftext|>


<commit_before>'use strict';

var fetchFeed = require('../fetch-feed');
var filterFeed = require('../filter-feed');
var patterns = require('../util').patterns;
var url = require('url');

var rScorePrefix = /^(\[\w+\]\s)?\d+\s+\S+\s+/;

function createDomainSuffix(entry) {
  var link = entry.find('link', 'href') || '';
  return (!link.length ? '' : ' ('+link.match(patterns.domain)[1]+')');
}

function transformMeta(root) {
  root.transformContent('title', { to: 'Hacker News (filtered)' });
}

function transformTitle(entry) {
  function replace(match) {
    // No score.
    var replaced = match.replace(rScorePrefix, '$1');
    // Add domain if any.
    replaced += createDomainSuffix(entry);
    return replaced;
  }
  entry.transformContent('title', { to: replace });
}

module.exports = function(config, request, response) {
  config.originalURL = 'http://hnapp.com/rss?q='+ config.hnappQuery;
  config.url = url.format({
    protocol: 'http', host: request.headers.host, pathname: config.name
  });

  fetchFeed({
    url: config.originalURL,
    onResponse: function(resFetch, data) {
      response.setHeader('Content-Type', resFetch.headers['content-type']);

      filterFeed({
        config: config,
        data: data,
        findId: function(entry) { return entry.find('id'); },
        findLink: function(entry) { return entry.find('link', 'href'); },
        transformEntry: transformTitle,
        transformMeta: transformMeta,
        verbose: true,
        onDone: function(data) {
          response.end(data);
        }
      });
    },
    onError: function(e) {
      response.end(e.message);
    }
  });
};
<commit_msg>Link to dark-mode viewer for comments [HN]<commit_after>'use strict';

var fetchFeed = require('../fetch-feed');
var filterFeed = require('../filter-feed');
var patterns = require('../util').patterns;
var url = require('url');

var rCommentsURL = /news.ycombinator.com\/item\?id=/;
var rScorePrefix = /^(\[\w+\]\s)?\d+\s+\S+\s+/;

function createDomainSuffix(entry) {
  var link = entry.find('link', 'href') || '';
  return (!link.length ? '' : ' ('+link.match(patterns.domain)[1]+')');
}

function transformContent(entry) {
  function replace(match) {
    var replaced = match.replace(rCommentsURL, 'hn.premii.com/#/comments/');
    return replaced;
  }
  entry.transformContent('content', { to: replace });
}

function transformMeta(root) {
  root.transformContent('title', { to: 'Hacker News (filtered)' });
}

function transformTitle(entry) {
  function replace(match) {
    // No score.
    var replaced = match.replace(rScorePrefix, '$1');
    // Add domain if any.
    replaced += createDomainSuffix(entry);
    return replaced;
  }
  entry.transformContent('title', { to: replace });
}

module.exports = function(config, request, response) {
  config.originalURL = 'http://hnapp.com/rss?q='+ config.hnappQuery;
  config.url = url.format({
    protocol: 'http', host: request.headers.host, pathname: config.name
  });

  fetchFeed({
    url: config.originalURL,
    onResponse: function(resFetch, data) {
      response.setHeader('Content-Type', resFetch.headers['content-type']);

      filterFeed({
        config: config,
        data: data,
        findId: function(entry) { return entry.find('id'); },
        findLink: function(entry) { return entry.find('link', 'href'); },
        transformEntry: function(entry) {
          transformTitle(entry);
          transformContent(entry);
        },
        transformMeta: transformMeta,
        verbose: true,
        onDone: function(data) {
          response.end(data);
        }
      });
    },
    onError: function(e) {
      response.end(e.message);
    }
  });
};
<|endoftext|>


<commit_before>/**
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * A self-contained modal library
 */
(function(window, document) {
    "use strict";

    /** Returns whether a value is a dom node */
    function isNode(value) {
        if ( typeof Node === "object" ) {
            return value instanceof Node;
        }
        else {
            return value &&
                typeof value === "object" &&
                typeof value.nodeType === "number";
        }
    }

    /** Returns whether a value is a string */
    function isString(value) {
        return typeof value === "string";
    }

    /**
     * Generates observable objects that can be watched and triggered
     */
    function observable() {
        var callbacks = [];
        return {
            watch: callbacks.push.bind(callbacks),
            trigger: function( modal ) {

                var unprevented = true;
                var event = {
                    preventDefault: function preventDefault () {
                        unprevented = false;
                    }
                };

                for (var i = 0; i < callbacks.length; i++) {
                    callbacks[i](modal, event);
                }

                return unprevented;
            }
        };
    }

    /**
     * A small interface for creating and managing a dom element
     */
    function Elem( elem ) {
        this.elem = elem;
    }

    /**
     * Creates a new div
     */
    Elem.div = function ( parent ) {
        var elem = document.createElement('div');
        (parent || document.body).appendChild(elem);
        return new Elem(elem);
    };

    Elem.prototype = {

        /** Creates a child of this node */
        child: function () {
            return Elem.div(this.elem);
        },

        /** Applies a set of styles to an element */
        stylize: function(styles) {
            styles = styles || {};

            if ( typeof styles.opacity !== "undefined" ) {
                styles.filter =
                    "alpha(opacity=" + (styles.opacity * 100) + ")";
            }

            for (var prop in styles) {
                if (styles.hasOwnProperty(prop)) {
                    this.elem.style[prop] = styles[prop];
                }
            }

            return this;
        },

        /** Adds a class name */
        clazz: function (clazz) {
            this.elem.className += " " + clazz;
            return this;
        },

        /** Sets the HTML */
        html: function (content) {
            if ( isNode(content) ) {
                this.elem.appendChild( content );
            }
            else {
                this.elem.innerHTML = content;
            }
            return this;
        },

        /** Returns the width of this element */
        getWidth: function () {
            return this.elem.clientWidth;
        },

        /** Adds a click handler to this element */
        onClick: function(callback) {
            if (this.elem.attachEvent) {
                this.elem.attachEvent('onclick', callback);
            }
            else {
                this.elem.addEventListener('click', callback);
            }
            return this;
        },

        /** Removes this element from the DOM */
        destroy: function() {
            document.body.removeChild(this.elem);
        },

        /** Hides this element */
        hide: function() {
            this.elem.style.display = "none";
        },

        /** Shows this element */
        show: function() {
            this.elem.style.display = "block";
        },

        /** Executes a callback on all the ancestors of an element */
        anyAncestor: function ( predicate ) {
            var elem = this.elem;
            while ( elem ) {
                if ( predicate( new Elem(elem) ) ) {
                    return true;
                }
                else {
                    elem = elem.parentNode;
                }
            }
            return false;
        }
    };

    /** Generates the grey-out effect */
    function buildOverlay( getOption, close ) {
        return Elem.div()
            .clazz("pico-overlay")
            .clazz( getOption("overlayClass", "") )
            .stylize({
                display: "block",
                position: "fixed",
                top: "0px",
                left: "0px",
                height: "100%",
                width: "100%",
                zIndex: 10000
            })
            .stylize(getOption('overlayStyles', {
                opacity: 0.5,
                background: "#000"
            }))
            .onClick(function () {
                if ( getOption('overlayClose', true) ) {
                    close();
                }
            });
    }

    /** Builds the content of a modal */
    function buildModal( getOption, close ) {
        var elem = Elem.div()
            .clazz("pico-content")
            .clazz( getOption("modalClass", "") )
            .stylize({
                display: 'block',
                position: 'fixed',
                zIndex: 10001,
                left: "50%",
                top: "50px"
            })
            .html( getOption('content') )
            .onClick(function (event) {
                var isCloseClick = new Elem(event.target)
                    .anyAncestor(function (elem) {
                        return /\bpico-close\b/.test(elem.elem.className);
                    });
                if ( isCloseClick ) {
                    close();
                }
            });

        var width = getOption('width', elem.getWidth());

        elem
            .stylize({
                width: width + "px",
                margin: "0 0 0 " + (-(width / 2) + "px")
            })
            .stylize( getOption('modalStyles', {
                backgroundColor: "white",
                padding: "20px",
                borderRadius: "5px"
            }) );

        return elem;
    }

    /** Builds the close button */
    function buildClose ( elem, getOption ) {
        if ( getOption('closeButton', true) ) {
            return elem.child()
                .html( getOption('closeHtml', "×") )
                .clazz("pico-close")
                .clazz( getOption("closeClass") )
                .stylize( getOption('closeStyles', {
                    borderRadius: "2px",
                    cursor: "pointer",
                    height: "15px",
                    width: "15px",
                    position: "absolute",
                    top: "5px",
                    right: "5px",
                    fontSize: "16px",
                    textAlign: "center",
                    lineHeight: "15px",
                    background: "#CCC"
                }) );
        }
    }

    /** Builds a method that calls a method and returns an element */
    function buildElemAccessor( builder ) {
        return function () {
            return builder().elem;
        };
    }

    /**
     * Displays a modal
     */
    function picoModal(options) {

        if ( isString(options) || isNode(options) ) {
            options = { content: options };
        }

        var afterCreateEvent = observable();
        var beforeShowEvent = observable();
        var afterShowEvent = observable();
        var beforeCloseEvent = observable();
        var afterCloseEvent = observable();

        /**
         * Returns a named option if it has been explicitly defined. Otherwise,
         * it returns the given default value
         */
        function getOption ( opt, defaultValue ) {
            var value = options[opt];
            if ( typeof value === "function" ) {
                value = value( defaultValue );
            }
            return value === undefined ? defaultValue : value;
        }

        /** Hides this modal */
        function forceClose () {
            shadowElem().hide();
            modalElem().hide();
            afterCloseEvent.trigger(iface);
        }

        /** Gracefully hides this modal */
        function close () {
            if ( beforeCloseEvent.trigger(iface) ) {
                forceClose();
            }
        }

        /** Wraps a method so it returns the modal interface */
        function returnIface ( callback ) {
            return function () {
                callback.apply(this, arguments);
                return iface;
            };
        }

        // The constructed dom nodes
        var built;

        /** Builds a method that calls a method and returns an element */
        function build ( name ) {
            if ( !built ) {
                var modal = buildModal(getOption, close);
                built = {
                    modal: modal,
                    overlay: buildOverlay(getOption, close),
                    close: buildClose(modal, getOption)
                };
                afterCreateEvent.trigger(iface);
            }
            return built[name];
        }

        var modalElem = build.bind(window, 'modal');
        var shadowElem = build.bind(window, 'overlay');
        var closeElem = build.bind(window, 'close');

        var iface = {

            /** Returns the wrapping modal element */
            modalElem: buildElemAccessor(modalElem),

            /** Returns the close button element */
            closeElem: buildElemAccessor(closeElem),

            /** Returns the overlay element */
            overlayElem: buildElemAccessor(shadowElem),

            /** Shows this modal */
            show: function () {
                if ( beforeShowEvent.trigger(iface) ) {
                    shadowElem().show();
                    closeElem();
                    modalElem().show();
                    afterShowEvent.trigger(iface);
                }
                return this;
            },

            /** Hides this modal */
            close: returnIface(close),

            /**
             * Force closes this modal. This will not call beforeClose
             * events and will just immediately hide the modal
             */
            forceClose: returnIface(forceClose),

            /** Destroys this modal */
            destroy: function () {
                modalElem = modalElem().destroy();
                shadowElem = shadowElem().destroy();
                closeElem = undefined;
            },

            /**
             * Updates the options for this modal. This will only let you
             * change options that are re-evaluted regularly, such as
             * `overlayClose`.
             */
            options: function ( opts ) {
                options = opts;
            },

            /** Executes after the DOM nodes are created */
            afterCreate: returnIface(afterCreateEvent.watch),

            /** Executes a callback before this modal is closed */
            beforeShow: returnIface(beforeShowEvent.watch),

            /** Executes a callback after this modal is shown */
            afterShow: returnIface(afterShowEvent.watch),

            /** Executes a callback before this modal is closed */
            beforeClose: returnIface(beforeCloseEvent.watch),

            /** Executes a callback after this modal is closed */
            afterClose: returnIface(afterCloseEvent.watch)
        };

        return iface;
    }

    if ( typeof window.define === "function" && window.define.amd ) {
        window.define(function () {
            return picoModal;
        });
    }
    else {
        window.picoModal = picoModal;
    }

}(window, document));
<commit_msg>Add aria dialog role<commit_after>/**
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * A self-contained modal library
 */
(function(window, document) {
    "use strict";

    /** Returns whether a value is a dom node */
    function isNode(value) {
        if ( typeof Node === "object" ) {
            return value instanceof Node;
        }
        else {
            return value &&
                typeof value === "object" &&
                typeof value.nodeType === "number";
        }
    }

    /** Returns whether a value is a string */
    function isString(value) {
        return typeof value === "string";
    }

    /**
     * Generates observable objects that can be watched and triggered
     */
    function observable() {
        var callbacks = [];
        return {
            watch: callbacks.push.bind(callbacks),
            trigger: function( modal ) {

                var unprevented = true;
                var event = {
                    preventDefault: function preventDefault () {
                        unprevented = false;
                    }
                };

                for (var i = 0; i < callbacks.length; i++) {
                    callbacks[i](modal, event);
                }

                return unprevented;
            }
        };
    }

    /**
     * A small interface for creating and managing a dom element
     */
    function Elem( elem ) {
        this.elem = elem;
    }

    /**
     * Creates a new div
     */
    Elem.div = function ( parent ) {
        var elem = document.createElement('div');
        (parent || document.body).appendChild(elem);
        return new Elem(elem);
    };

    Elem.prototype = {

        /** Creates a child of this node */
        child: function () {
            return Elem.div(this.elem);
        },

        /** Applies a set of styles to an element */
        stylize: function(styles) {
            styles = styles || {};

            if ( typeof styles.opacity !== "undefined" ) {
                styles.filter =
                    "alpha(opacity=" + (styles.opacity * 100) + ")";
            }

            for (var prop in styles) {
                if (styles.hasOwnProperty(prop)) {
                    this.elem.style[prop] = styles[prop];
                }
            }

            return this;
        },

        /** Adds a class name */
        clazz: function (clazz) {
            this.elem.className += " " + clazz;
            return this;
        },

        /** Sets the HTML */
        html: function (content) {
            if ( isNode(content) ) {
                this.elem.appendChild( content );
            }
            else {
                this.elem.innerHTML = content;
            }
            return this;
        },

        /** Returns the width of this element */
        getWidth: function () {
            return this.elem.clientWidth;
        },

        /** Adds a click handler to this element */
        onClick: function(callback) {
            if (this.elem.attachEvent) {
                this.elem.attachEvent('onclick', callback);
            }
            else {
                this.elem.addEventListener('click', callback);
            }
            return this;
        },

        /** Removes this element from the DOM */
        destroy: function() {
            document.body.removeChild(this.elem);
        },

        /** Hides this element */
        hide: function() {
            this.elem.style.display = "none";
        },

        /** Shows this element */
        show: function() {
            this.elem.style.display = "block";
        },

        /** Sets an attribute on this element */
        attr: function ( name, value ) {
            this.elem.setAttribute(name, value);
            return this;
        },

        /** Executes a callback on all the ancestors of an element */
        anyAncestor: function ( predicate ) {
            var elem = this.elem;
            while ( elem ) {
                if ( predicate( new Elem(elem) ) ) {
                    return true;
                }
                else {
                    elem = elem.parentNode;
                }
            }
            return false;
        }
    };

    /** Generates the grey-out effect */
    function buildOverlay( getOption, close ) {
        return Elem.div()
            .clazz("pico-overlay")
            .clazz( getOption("overlayClass", "") )
            .stylize({
                display: "block",
                position: "fixed",
                top: "0px",
                left: "0px",
                height: "100%",
                width: "100%",
                zIndex: 10000
            })
            .stylize(getOption('overlayStyles', {
                opacity: 0.5,
                background: "#000"
            }))
            .onClick(function () {
                if ( getOption('overlayClose', true) ) {
                    close();
                }
            });
    }

    /** Builds the content of a modal */
    function buildModal( getOption, close ) {
        var elem = Elem.div()
            .clazz("pico-content")
            .clazz( getOption("modalClass", "") )
            .stylize({
                display: 'block',
                position: 'fixed',
                zIndex: 10001,
                left: "50%",
                top: "50px"
            })
            .html( getOption('content') )
            .attr("role", "dialog")
            .onClick(function (event) {
                var isCloseClick = new Elem(event.target)
                    .anyAncestor(function (elem) {
                        return /\bpico-close\b/.test(elem.elem.className);
                    });
                if ( isCloseClick ) {
                    close();
                }
            });

        var width = getOption('width', elem.getWidth());

        elem
            .stylize({
                width: width + "px",
                margin: "0 0 0 " + (-(width / 2) + "px")
            })
            .stylize( getOption('modalStyles', {
                backgroundColor: "white",
                padding: "20px",
                borderRadius: "5px"
            }) );

        return elem;
    }

    /** Builds the close button */
    function buildClose ( elem, getOption ) {
        if ( getOption('closeButton', true) ) {
            return elem.child()
                .html( getOption('closeHtml', "×") )
                .clazz("pico-close")
                .clazz( getOption("closeClass") )
                .stylize( getOption('closeStyles', {
                    borderRadius: "2px",
                    cursor: "pointer",
                    height: "15px",
                    width: "15px",
                    position: "absolute",
                    top: "5px",
                    right: "5px",
                    fontSize: "16px",
                    textAlign: "center",
                    lineHeight: "15px",
                    background: "#CCC"
                }) );
        }
    }

    /** Builds a method that calls a method and returns an element */
    function buildElemAccessor( builder ) {
        return function () {
            return builder().elem;
        };
    }

    /**
     * Displays a modal
     */
    function picoModal(options) {

        if ( isString(options) || isNode(options) ) {
            options = { content: options };
        }

        var afterCreateEvent = observable();
        var beforeShowEvent = observable();
        var afterShowEvent = observable();
        var beforeCloseEvent = observable();
        var afterCloseEvent = observable();

        /**
         * Returns a named option if it has been explicitly defined. Otherwise,
         * it returns the given default value
         */
        function getOption ( opt, defaultValue ) {
            var value = options[opt];
            if ( typeof value === "function" ) {
                value = value( defaultValue );
            }
            return value === undefined ? defaultValue : value;
        }

        /** Hides this modal */
        function forceClose () {
            shadowElem().hide();
            modalElem().hide();
            afterCloseEvent.trigger(iface);
        }

        /** Gracefully hides this modal */
        function close () {
            if ( beforeCloseEvent.trigger(iface) ) {
                forceClose();
            }
        }

        /** Wraps a method so it returns the modal interface */
        function returnIface ( callback ) {
            return function () {
                callback.apply(this, arguments);
                return iface;
            };
        }

        // The constructed dom nodes
        var built;

        /** Builds a method that calls a method and returns an element */
        function build ( name ) {
            if ( !built ) {
                var modal = buildModal(getOption, close);
                built = {
                    modal: modal,
                    overlay: buildOverlay(getOption, close),
                    close: buildClose(modal, getOption)
                };
                afterCreateEvent.trigger(iface);
            }
            return built[name];
        }

        var modalElem = build.bind(window, 'modal');
        var shadowElem = build.bind(window, 'overlay');
        var closeElem = build.bind(window, 'close');

        var iface = {

            /** Returns the wrapping modal element */
            modalElem: buildElemAccessor(modalElem),

            /** Returns the close button element */
            closeElem: buildElemAccessor(closeElem),

            /** Returns the overlay element */
            overlayElem: buildElemAccessor(shadowElem),

            /** Shows this modal */
            show: function () {
                if ( beforeShowEvent.trigger(iface) ) {
                    shadowElem().show();
                    closeElem();
                    modalElem().show();
                    afterShowEvent.trigger(iface);
                }
                return this;
            },

            /** Hides this modal */
            close: returnIface(close),

            /**
             * Force closes this modal. This will not call beforeClose
             * events and will just immediately hide the modal
             */
            forceClose: returnIface(forceClose),

            /** Destroys this modal */
            destroy: function () {
                modalElem = modalElem().destroy();
                shadowElem = shadowElem().destroy();
                closeElem = undefined;
            },

            /**
             * Updates the options for this modal. This will only let you
             * change options that are re-evaluted regularly, such as
             * `overlayClose`.
             */
            options: function ( opts ) {
                options = opts;
            },

            /** Executes after the DOM nodes are created */
            afterCreate: returnIface(afterCreateEvent.watch),

            /** Executes a callback before this modal is closed */
            beforeShow: returnIface(beforeShowEvent.watch),

            /** Executes a callback after this modal is shown */
            afterShow: returnIface(afterShowEvent.watch),

            /** Executes a callback before this modal is closed */
            beforeClose: returnIface(beforeCloseEvent.watch),

            /** Executes a callback after this modal is closed */
            afterClose: returnIface(afterCloseEvent.watch)
        };

        return iface;
    }

    if ( typeof window.define === "function" && window.define.amd ) {
        window.define(function () {
            return picoModal;
        });
    }
    else {
        window.picoModal = picoModal;
    }

}(window, document));
<|endoftext|>


<commit_before>var m            = require('mithril')
var fuzzyMatch   = require('../../../node_modules/fuzzysearch-js/js/FuzzySearch.js')
var Fuzzy        = require('../models/Fuzzy.js')

var AutocompleteInput = module.exports = {}

Function.prototype.papp = function () {
  var slice = Array.prototype.slice
  var fn = this
  var args = slice.call(arguments)
  return function () {
    return fn.apply(this, args.concat(slice.call(arguments)))
  }
};

Function.prototype.chill = function() {
  var fn = this
  return function(e) {
    e.preventDefault()
    return fn()
  }
};

// attr.options is expected to be an array of objects
AutocompleteInput.controller = function (attrs) {
  var ctrl = this;
  var inititalOptions = attrs.initialOptions || [];

  ctrl.isFocused = m.prop(false);
  ctrl.dropdownIndex = m.prop(0);
  ctrl.mode = m.prop('keyboard');
  ctrl.query = m.prop(null);

  ctrl.options = m.prop([]);

  ctrl.select = function () {
    var opt = ctrl.options()[ ctrl.dropdownIndex() ]
    if (opt) {
      attrs.onSelect(opt.id ? opt.id : opt.uid) // Send back id
      pendingInputValue = attrs.optionView(opt)
    }
    ctrl.reset()
    // ctrl.isFocused(true)
    if (ctrl.mode() === 'mouse') blur = true;
  }

  ctrl.reset = function (value) {
    clearTimeout(lastSearchTimeout);
    ctrl.dropdownIndex(0);
    ctrl.query(value);
    m.redraw();
  };

  ctrl.onkeydown = function (e) {
    var key = e.keyCode || e.which;
    if (key !== 38 && key !== 40 && key !== 13 && key !== 27) return;
    e.preventDefault();
    ctrl.mode('keyboard')

    var idx = +ctrl.dropdownIndex()
    if (key === 38) ctrl.dropdownIndex(Math.max(idx - 1, 0))
    if (key === 40) ctrl.dropdownIndex(Math.min(idx + 1, ctrl.options().length - 1))
    if (key === 13) ctrl.select()
    if (key === 27) { blur = true; ctrl.isFocused(false) }
  };

  var lastSearchTimeout = null
  ctrl.onkeyup = function (e) {
    var query = ctrl.query()
    var newQuery = e.currentTarget.value;

    if (newQuery.length < 1) {
      ctrl.query(null)
      ctrl.options(inititalOptions)
    }
    else if (newQuery !== query) {

      ctrl.mode('keyboard');
      ctrl.dropdownIndex(0);
      ctrl.query(newQuery);

      // Fuzzy.companySearch(newQuery).then(ctrl.options);
      Fuzzy.search(attrs.search, newQuery).then(ctrl.options);
    }
  }

  var blur = false
  var pendingInputValue = null
  ctrl.cleanInput = function (input) {

    if (blur) {
      input.blur(); ctrl.reset(); blur = false
    }
    if (pendingInputValue) {
      input.value = pendingInputValue
      pendingInputValue = null;
    }
  }

  // Format: [['myOptionValue', 'myOptionDisplayText', 'myoptiondisplaytext'], ...]
  function updateAllOptions (options) {
    inititalOptions = options.map(function(op) {
      return [ op[attrs.idAttr], op[attrs.searchAttr], op[attrs.searchAttr].toLowerCase() ]
    })
    if (ctrl.query() === null) ctrl.options(inititalOptions)
  }
}

AutocompleteInput.view = function (ctrl, attrs) {

  var ddIdx = ctrl.dropdownIndex()
  var mode = ctrl.mode()
  var queryRegex = ctrl.query() && new RegExp('(.*)('+ctrl.query()+')(.*)', 'i')

  return m('.row', [

    m('.autocomplete-input.col.s12', [

    m('input[type=text]#', {
      key: 'autocomplete',
      config: ctrl.cleanInput,
      onkeyup: ctrl.onkeyup,
      onkeydown: ctrl.onkeydown,
      onfocus: ctrl.isFocused.papp(true),
      onblur: ctrl.isFocused.papp(false),
      placeholder: attrs.placeholder
    }),
    ]),
    ctrl.isFocused() ?
      m('.autocomplete-input--select-drop.fixed', [
        m('ul', { class: mode, onmousedown: ctrl.select.chill() }, renderOptions(ctrl.query()))
      ])
      : null,
        ctrl.options().length === 0 && ctrl.query ? attrs.route : null
    ])

  function renderOptions (query) {
    if (ctrl.options().length === 0 && query !== null) {
      return m('.row.absolute.nothing',
        m('li.right-align.nothing',
          m('i', "No matches found.")
        ))
   	} else {
      return ctrl.options().map(optionView)
  	}
  }

  function optionView (opt, i) {
    return m('li', {
      'class': (mode === 'keyboard' && ddIdx == i) ? 'active.z-index' : 'no-hover.z-index',
      'data-idx': i
    }, attrs.optionView(opt) )
  }

  function selectHovered (e) {
    if (e.target.tagName !== 'LI') return;
    var idx = e.target.getAttribute('data-idx')
    ctrl.dropdownIndex(idx)
    ctrl.mode('mouse')
  }
}<commit_msg>fixing fuzzy search grabbing accurate selection<commit_after>var m            = require('mithril')
var fuzzyMatch   = require('../../../node_modules/fuzzysearch-js/js/FuzzySearch.js')
var Fuzzy        = require('../models/Fuzzy.js')

var AutocompleteInput = module.exports = {}

Function.prototype.papp = function () {
  var slice = Array.prototype.slice
  var fn = this
  var args = slice.call(arguments)
  return function () {
    return fn.apply(this, args.concat(slice.call(arguments)))
  }
};

Function.prototype.chill = function() {
  var fn = this
  return function(e) {
    var val = e.target.getAttribute('data-idx');
    e.preventDefault()
    return fn(val)
  }
};

// attr.options is expected to be an array of objects
AutocompleteInput.controller = function (attrs) {
  var ctrl = this;
  var initialOptions = attrs.initialOptions || [];

  ctrl.isFocused = m.prop(false);
  ctrl.dropdownIndex = m.prop(0);
  ctrl.mode = m.prop('keyboard');
  ctrl.query = m.prop(null);

  ctrl.options = m.prop([]);

  ctrl.select = function (val) {
    var val = val || 0;
    var opt = ctrl.options()[ ctrl.dropdownIndex(val) ];
    if (opt) {

      attrs.onSelect(opt.id ? opt.id : opt.uid) // Send back id
      pendingInputValue = attrs.optionView(opt)

    }
    ctrl.reset()
    // ctrl.isFocused(true)
    if (ctrl.mode() === 'mouse') blur = true;
  }

  ctrl.reset = function (value) {
    clearTimeout(lastSearchTimeout);
    ctrl.dropdownIndex(0);
    ctrl.query(value);
    m.redraw();
  };

  ctrl.onkeydown = function (e) {
    var key = e.keyCode || e.which;
    if (key !== 38 && key !== 40 && key !== 13 && key !== 27) return;
    e.preventDefault();
    ctrl.mode('keyboard')

    var idx = +ctrl.dropdownIndex()
    if (key === 38) ctrl.dropdownIndex(Math.max(idx - 1, 0))
    if (key === 40) ctrl.dropdownIndex(Math.min(idx + 1, ctrl.options().length - 1))
    if (key === 13) ctrl.select()
    if (key === 27) { blur = true; ctrl.isFocused(false) }
  };

  var lastSearchTimeout = null
  ctrl.onkeyup = function (e) {
    var query = ctrl.query()
    var newQuery = e.currentTarget.value;

    if (newQuery.length < 1) {
      ctrl.query(null)
      ctrl.options(initialOptions)
    }
    else if (newQuery !== query) {

      ctrl.mode('keyboard');
      ctrl.dropdownIndex(0);
      ctrl.query(newQuery);

      // Fuzzy.companySearch(newQuery).then(ctrl.options);
      Fuzzy.search(attrs.search, newQuery).then(ctrl.options);
    }
  }

  var blur = false
  var pendingInputValue = null
  ctrl.cleanInput = function (input) {

    if (blur) {
      input.blur(); ctrl.reset(); blur = false
    }
    if (pendingInputValue) {
      input.value = pendingInputValue
      pendingInputValue = null;
    }
  }

  // Format: [['myOptionValue', 'myOptionDisplayText', 'myoptiondisplaytext'], ...]
  function updateAllOptions (options) {
    initialOptions = options.map(function(op) {
      return [ op[attrs.idAttr], op[attrs.searchAttr], op[attrs.searchAttr].toLowerCase() ]
    })
    if (ctrl.query() === null) ctrl.options(initialOptions)
  }
}

AutocompleteInput.view = function (ctrl, attrs) {

  var ddIdx = ctrl.dropdownIndex()
  var mode = ctrl.mode()
  var queryRegex = ctrl.query() && new RegExp('(.*)('+ctrl.query()+')(.*)', 'i')

  return m('.row', [

    m('.autocomplete-input.col.s12', [

    m('input[type=text]#', {
      key: 'autocomplete',
      config: ctrl.cleanInput,
      onkeyup: ctrl.onkeyup,
      onkeydown: ctrl.onkeydown,
      onfocus: ctrl.isFocused.papp(true),
      onblur: ctrl.isFocused.papp(false),
      placeholder: attrs.placeholder
    }),
    ]),
    ctrl.isFocused() ?
      m('.autocomplete-input--select-drop.fixed', [
        m('ul', { class: mode, onmousedown: ctrl.select.chill() }, renderOptions(ctrl.query()))
      ])
      : null,
        ctrl.options().length === 0 && ctrl.query ? attrs.route : null
    ])

  function renderOptions (query) {
    if (ctrl.options().length === 0 && query !== null) {
      return m('.row.nothing',
        m('li.right-align.nothing',
          m('i', "No matches found.")
        ))
   	} else {
      return ctrl.options().map(optionView)
  	}
  }

  function optionView (opt, i) {
    return m('li', {
      'class': (mode === 'keyboard' && ddIdx == i) ? 'active.z-index' : 'no-hover.z-index',
      'data-idx': i
    }, attrs.optionView(opt) )
  }
}<|endoftext|>


<commit_before>sceneData["tracking1"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality."
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text:"The sensors kept reporting an unidentified pollutant."
	},{
		application: loadingFunctions.text,
		text:"It wasn’t stationary, but it was always very localised."
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack."
	}{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I helped set up the prank.",
		  	goal: "tracking1_outro"
		  },{
		  	text: "The prank completely fooled me",
		  	goal: "tracking1_outro"
		  },{
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking1_outro"
		  }
		]
	}
];

sceneData["tracking1_outro"] = [
	{
		application: loadingFunctions.text,
		text: "The entire campus was evacuated for an entire day, the prank even made the national news.  Considering the all the panic about the terrorists I’m surprised we got off so lightly."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "It made a memorable end to my undergraduate degree.",
			goal: "exam_1_1"
		  }
		]
	}
];

sceneData["tracking2"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality. The sensors kept reporting an unidentified pollutant. It wasn’t stationary, but it was always very localised"
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack. I was only involved because I was dating Arness."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I played the part of a victim",
		  	goal: "tracking2_victim"
		  },{
		  	text: "The prank didn't fool anyone.",
		  	goal: "tracking2_fool"
		  },{
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking2_evacuation"
		  }
		]
	}
];

sceneData["tracking2_victim"] = [
	{
		application: loadingFunctions.text,
		text: "I was meant to be a zombie, but I didn’t fool anyone."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I wait, there was something in the station.",
		  	goal: "tracking2"
		  }
		]
	}
];

sceneData["tracking2_fool"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "The pollutant moved around the building, like it was tracking something.",
		  	goal: "tracking2"
		  }
		]
	}
];

sceneData["tracking2_evacuation"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.text,
		text: "I signaled to the station's crew about the pollutant, told them where was safe."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe there was something I studied which could help explain what is happening.",
		  	goal: "exam_2_1"
		  }
		]
	}
];

sceneData["tracking3"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality. The sensors kept reporting an unidentified pollutant. It wasn’t stationary, but it was always very localised"
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack. I was only involved because I was dating Arness."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I played the part of a victim",
		  	goal: "tracking3_victim"
		  }, {
		  	text: "The prank didn't fool anyone.",
		  	goal: "tracking3_fool"
		  }, {
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking3_evacuation"
		  }
		]
	}
];

sceneData["tracking3_victim"] = [
	{
		application: loadingFunctions.text,
		text: "I was meant to be a zombie, but I didn’t fool anyone."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I wait, there was something in the station.",
		  	goal: "tracking3"
		  }
		]
	}
];

sceneData["tracking3_fool"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "The pollutant moved around the building, like it was tracking something.",
		  	goal: "tracking3"
		  }
		]
	}
];

sceneData["tracking3_evacuation"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.text,
		text: "I signaled to the station's crew about the pollutant, told them where was safe. I didn’t want them to be herded."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe there was something I studied which could help explain what is happening.",
		  	goal: "tracking3_1"
		  }
		]
	}
];

sceneData["tracking3_1"] = [
	{
		application: loadingFunctions.text,
		text: "I could track the gas by the station sensors.  It was moving.  I was finding it hard to focus, my eyes would lose focus. It couldn’t be the gas, it was no where near me."
	},{
		application: loadingFunctions.text,
		text: "I was so tired. So tired."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I led the crew to the loading bay, it was safe there.",
		  	goal: "tracking3_loading_bay"
		  },{
		  	text: "I blacked out, for a while, but the crew managed to get to the loading bay",
		  	goal: "tracking3_loading_bay"
		  },{
		  	text: "The gas wasn’t stalking, it was herding.  I tried to tell the crew, tried so hard to contact Blackwood, tell him of the danger.",
		  	goal: "tracking3_danger"
		  }
		]
	}
];

sceneData["tracking3_loading_bay"] = [
	{
		application: loadingFunctions.text,
		text: "There was no safety."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I needed to get the crew to the loading bay",
		  	goal: "tracking3_1"
		  }
		]
	}
];

sceneData["tracking3_danger"] = [
	{
		application: loadingFunctions.text,
		text: "I couldn’t contact the crew. "
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe I could open an airlock, suck the monster into space.",
		  	goal: "exam_3_1"
		  }
		]
	}
];

<commit_msg>fixed another clause<commit_after>sceneData["tracking1"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality."
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text:"The sensors kept reporting an unidentified pollutant."
	},{
		application: loadingFunctions.text,
		text:"It wasn’t stationary, but it was always very localised."
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I helped set up the prank.",
		  	goal: "tracking1_outro"
		  },{
		  	text: "The prank completely fooled me",
		  	goal: "tracking1_outro"
		  },{
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking1_outro"
		  }
		]
	}
];

sceneData["tracking1_outro"] = [
	{
		application: loadingFunctions.text,
		text: "The entire campus was evacuated for an entire day, the prank even made the national news.  Considering the all the panic about the terrorists I’m surprised we got off so lightly."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "It made a memorable end to my undergraduate degree.",
			goal: "exam_1_1"
		  }
		]
	}
];

sceneData["tracking2"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality. The sensors kept reporting an unidentified pollutant. It wasn’t stationary, but it was always very localised"
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack. I was only involved because I was dating Arness."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I played the part of a victim",
		  	goal: "tracking2_victim"
		  },{
		  	text: "The prank didn't fool anyone.",
		  	goal: "tracking2_fool"
		  },{
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking2_evacuation"
		  }
		]
	}
];

sceneData["tracking2_victim"] = [
	{
		application: loadingFunctions.text,
		text: "I was meant to be a zombie, but I didn’t fool anyone."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I wait, there was something in the station.",
		  	goal: "tracking2"
		  }
		]
	}
];

sceneData["tracking2_fool"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "The pollutant moved around the building, like it was tracking something.",
		  	goal: "tracking2"
		  }
		]
	}
];

sceneData["tracking2_evacuation"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.text,
		text: "I signaled to the station's crew about the pollutant, told them where was safe."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe there was something I studied which could help explain what is happening.",
		  	goal: "exam_2_1"
		  }
		]
	}
];

sceneData["tracking3"] = [
	{
		application: loadingFunctions.text,
		text: "I first spotted the anomaly in the station reports for air quality. The sensors kept reporting an unidentified pollutant. It wasn’t stationary, but it was always very localised"
	},{
		application: loadingFunctions.picture,
		path: "next.png"
	},{
		application: loadingFunctions.text,
		text: "The reading reminded me of was was a prank by some of Professor Ellery’s biochemistry students, where we faked a gas attack. I was only involved because I was dating Arness."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I played the part of a victim",
		  	goal: "tracking3_victim"
		  }, {
		  	text: "The prank didn't fool anyone.",
		  	goal: "tracking3_fool"
		  }, {
		  	text: "The prank caused an evacuation of the Beauly Campus",
		  	goal: "tracking3_evacuation"
		  }
		]
	}
];

sceneData["tracking3_victim"] = [
	{
		application: loadingFunctions.text,
		text: "I was meant to be a zombie, but I didn’t fool anyone."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I wait, there was something in the station.",
		  	goal: "tracking3"
		  }
		]
	}
];

sceneData["tracking3_fool"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "The pollutant moved around the building, like it was tracking something.",
		  	goal: "tracking3"
		  }
		]
	}
];

sceneData["tracking3_evacuation"] = [
	{
		application: loadingFunctions.text,
		text: "I wait, there was something in the station."
	},{
		application: loadingFunctions.text,
		text: "I signaled to the station's crew about the pollutant, told them where was safe. I didn’t want them to be herded."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe there was something I studied which could help explain what is happening.",
		  	goal: "tracking3_1"
		  }
		]
	}
];

sceneData["tracking3_1"] = [
	{
		application: loadingFunctions.text,
		text: "I could track the gas by the station sensors.  It was moving.  I was finding it hard to focus, my eyes would lose focus. It couldn’t be the gas, it was no where near me."
	},{
		application: loadingFunctions.text,
		text: "I was so tired. So tired."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
		  	text: "I led the crew to the loading bay, it was safe there.",
		  	goal: "tracking3_loading_bay"
		  },{
		  	text: "I blacked out, for a while, but the crew managed to get to the loading bay",
		  	goal: "tracking3_loading_bay"
		  },{
		  	text: "The gas wasn’t stalking, it was herding.  I tried to tell the crew, tried so hard to contact Blackwood, tell him of the danger.",
		  	goal: "tracking3_danger"
		  }
		]
	}
];

sceneData["tracking3_loading_bay"] = [
	{
		application: loadingFunctions.text,
		text: "There was no safety."
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "I needed to get the crew to the loading bay",
		  	goal: "tracking3_1"
		  }
		]
	}
];

sceneData["tracking3_danger"] = [
	{
		application: loadingFunctions.text,
		text: "I couldn’t contact the crew. "
	},{
		application: loadingFunctions.choices,
		choices: [
		  {
			text: "Maybe I could open an airlock, suck the monster into space.",
		  	goal: "exam_3_1"
		  }
		]
	}
];

<|endoftext|>


<commit_before>/**
 * @ngdoc directive
 * @name angulargm.directive:gmMarkerClusterer
 * @element GM-MARKERS
 *
 * @description
 * A directive for clustering markers created by a `gmMarkers`.
 *
 * To use, add it as an attribute to a gm-markers directive.
 *
 * Be sure to include markerclusterer.js
 * (https://github.com/gmaps-marker-clusterer/gmaps-marker-clusterer)
 * in your project.
 *
 * Use the gmMarkerClustererOptions attribute of gmMarkers to
 * override the default clusterer options;
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMarkerClusterer#gmExecuteClustererMethod
 * @eventOf angulargm.directive:gmMarkerClusterer
 * @eventType listen on gmMarkerClusterer scope
 *
 * @description Broadcast to trigger execution of a
 * [MarkerClusterer method](https://gmaps-marker-clusterer.github.io/gmaps-marker-clusterer/#methods)
 *
 * @param {string} objectsName gmObjects expression for the desired clusterer.
 *
 * @param {object} options object containing the the MarkerClusterer method
 * to execute, the arguments to apply it with, and an event name to emit with
 * the results.
 *
 * @example
 * ```js
 * $scope.$broadcast('gmExecuteClustererMethod', {
 *    method: 'setMaxZoom',
 *    arguments: [12],
 *    callback: 'doneWithSetMaxZoom'
 * });
 * ```
 */

(function () {
  'use strict';

  angular.module('AngularGM').

    directive('gmMarkerClusterer',
    [function () {

      return {
        restrict: 'A',
        require: '^gmMap',
        link: function (scope, elem, attr, ctrl) {

          var hasInitialData = false;
          var markerClusterer;

          function init() {
            var scopeId = ctrl.getScopeIdByObjectsName(attr.gmObjects);
            var markersCollection = ctrl.getElementsByScopeId('marker', scopeId);
            var map = ctrl.getMap();
            var options = ctrl.getMarkerClustererOptions(attr.gmObjects);
            markerClusterer = new MarkerClusterer(map, [], options);

            angular.forEach(markersCollection, function (value, key) {
              // false as second argument to avoid redraws after each marker
              markerClusterer.addMarker(value, false);
            });

            markerClusterer.redraw();
          }

          scope.$on('gmMarkersUpdated', function (event, objectsName) {
            if (objectsName === attr.gmObjects) {
              if (!hasInitialData) {
                init();
                hasInitialData = true;
              }
              else if (markerClusterer) {
                markerClusterer.repaint();
              }
            }
          });

          scope.$on('gmMarkersAdded', function (event, objectsName, added) {
            if (hasInitialData && objectsName === attr.gmObjects && markerClusterer && added && added.length > 0) {
              angular.forEach(added, function (value, index) {
                // false as second argument to avoid redraws after each marker
                markerClusterer.addMarker(value.element, false);
              });
              markerClusterer.redraw();
            }
          });

          scope.$on('gmMarkersRemoved', function (event, objectsName, removed) {
            if (hasInitialData && objectsName === attr.gmObjects && markerClusterer && removed && removed.length > 0) {
              angular.forEach(removed, function (value, index) {
                markerClusterer.removeMarker(value.element);
              });
            }
          });

          scope.$on('gmExecuteClustererMethod', function (event, objectsName, options) {
            if (markerClusterer && options && options.method && markerClusterer[options.method] && objectsName && objectsName === attr.gmObjects) {
              var result = markerClusterer[options.method].apply(markerClusterer, options.arguments);
              if (options.callback) {
                scope.$emit(options.callback, result);
              }
            }
          });

        }
      };
    }]);
})();
<commit_msg>Emit gmMarkerClustererReady so you can tell when it is safe to use gmExecuteClustererMethod<commit_after>/**
 * @ngdoc directive
 * @name angulargm.directive:gmMarkerClusterer
 * @element GM-MARKERS
 *
 * @description
 * A directive for clustering markers created by a `gmMarkers`.
 *
 * To use, add it as an attribute to a gm-markers directive.
 *
 * Be sure to include markerclusterer.js
 * (https://github.com/gmaps-marker-clusterer/gmaps-marker-clusterer)
 * in your project.
 *
 * Use the gmMarkerClustererOptions attribute of gmMarkers to
 * override the default clusterer options;
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMarkerClusterer#gmMarkerClustererReady
 * @eventOf angulargm.directive:gmMarkerClusterer
 * @eventType emit on gmMarkerClusterer scope
 *
 * @description Emitted when the Marker Clusterer has finished initialization.
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMarkerClusterer#gmExecuteClustererMethod
 * @eventOf angulargm.directive:gmMarkerClusterer
 * @eventType listen on gmMarkerClusterer scope
 *
 * @description Broadcast to trigger execution of a
 * [MarkerClusterer method](https://gmaps-marker-clusterer.github.io/gmaps-marker-clusterer/#methods)
 *
 * @param {string} objectsName gmObjects expression for the desired clusterer.
 *
 * @param {object} options object containing the the MarkerClusterer method
 * to execute, the arguments to apply it with, and an event name to emit with
 * the results.
 *
 * @example
 * ```js
 * $scope.$broadcast('gmExecuteClustererMethod', {
 *    method: 'setMaxZoom',
 *    arguments: [12],
 *    callback: 'doneWithSetMaxZoom'
 * });
 * ```
 */

(function () {
  'use strict';

  angular.module('AngularGM').

    directive('gmMarkerClusterer',
    [function () {

      return {
        restrict: 'A',
        require: '^gmMap',
        link: function (scope, elem, attr, ctrl) {

          var hasInitialData = false;
          var markerClusterer;

          function init() {
            var scopeId = ctrl.getScopeIdByObjectsName(attr.gmObjects);
            var markersCollection = ctrl.getElementsByScopeId('marker', scopeId);
            var map = ctrl.getMap();
            var options = ctrl.getMarkerClustererOptions(attr.gmObjects);
            markerClusterer = new MarkerClusterer(map, [], options);

            angular.forEach(markersCollection, function (value, key) {
              // false as second argument to avoid redraws after each marker
              markerClusterer.addMarker(value, false);
            });

            markerClusterer.redraw();

            scope.$emit('gmMarkerClustererReady');
          }

          scope.$on('gmMarkersUpdated', function (event, objectsName) {
            if (objectsName === attr.gmObjects) {
              if (!hasInitialData) {
                init();
                hasInitialData = true;
              }
              else if (markerClusterer) {
                markerClusterer.repaint();
              }
            }
          });

          scope.$on('gmMarkersAdded', function (event, objectsName, added) {
            if (hasInitialData && objectsName === attr.gmObjects && markerClusterer && added && added.length > 0) {
              angular.forEach(added, function (value, index) {
                // false as second argument to avoid redraws after each marker
                markerClusterer.addMarker(value.element, false);
              });
              markerClusterer.redraw();
            }
          });

          scope.$on('gmMarkersRemoved', function (event, objectsName, removed) {
            if (hasInitialData && objectsName === attr.gmObjects && markerClusterer && removed && removed.length > 0) {
              angular.forEach(removed, function (value, index) {
                markerClusterer.removeMarker(value.element);
              });
            }
          });

          scope.$on('gmExecuteClustererMethod', function (event, objectsName, options) {
            if (markerClusterer && options && options.method && markerClusterer[options.method] && objectsName && objectsName === attr.gmObjects) {
              var result = markerClusterer[options.method].apply(markerClusterer, options.arguments);
              if (options.callback) {
                scope.$emit(options.callback, result);
              }
            }
          });

        }
      };
    }]);
})();
<|endoftext|>


<commit_before>import React from "react";
import * as globals from "./globals";

class TidCarousel extends React.Component {

  render() {
    return (

 Comments:

            {this.props.commentsToShow ? this.props.commentsToShow.map((c) => { return (

                {c.tid}

            )}) : ""}

    )
  }
}

export default TidCarousel;
<commit_msg>tid carousel now with paging...<commit_after>import React from "react";
import * as globals from "./globals";
import _ from "lodash";

class PaginateButton extends React.Component {

  render() {
    return (
      <button
        onClick={this.props.paginate}
        style={{
          marginLeft: this.props.leftSide ? 0 : 10,
          marginRight: this.props.leftSide ? 10 : 0,
          cursor: "pointer",
          border: "none",
          backgroundColor: "white",
          position: "relative",
          top: 5,
        }}>
        <svg width="20px" height="20px" viewBox="0 0 10 10" >
          {this.props.children}
        </svg>
      </button>
    )
  }

}

class TidCarousel extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      page: 0,
      perPage: 10,
    };
  }

  movePage (n) {
    return () => {
      console.log(this.state.page, this.state.perPage, n)
      if (this.state.page + n <= 0) {
        this.setState({page: 0}) /* min case */
      } else if (this.state.page + n > (this.props.commentsToShow.length / this.state.perPage)) {
        this.setState({page: ((this.props.commentsToShow.length / this.state.perPage) - 1)})
      } else {
        this.setState({page: this.state.page + n})
      }
    }
  }

  render() {

    return (

          <PaginateButton paginate={this.movePage(-10)} leftSide>
            <g transform="translate(-13.000000, -5.000000)">
              <g transform="translate(13.000000, 5.000000)">
                <polyline fill="rgb(180,180,180)" points="4.50549451 5 9.50549451 10 9.50549451 6.123234e-16"></polyline>
                <polyline fill="rgb(180,180,180)" points="-3.061617e-16 5 5 10 5 6.123234e-16"></polyline>
              </g>
            </g>
          </PaginateButton>
          <PaginateButton paginate={this.movePage(-1)} leftSide>
            <g  transform="translate(-13.000000, -5.000000)">
              <g transform="translate(13.000000, 5.000000)">
                <polyline fill="rgb(180,180,180)" points="-3.061617e-16 5 5 10 5 6.123234e-16"></polyline>
              </g>
            </g>
          </PaginateButton>

          {
            _.map(this.props.commentsToShow.slice((this.state.page * this.state.perPage), ((this.state.page * this.state.perPage) + this.state.perPage)), (c) => { return (
              <span
                onClick={this.props.handleCommentClick(c)}
                style={{
                  cursor: "pointer",
                  marginRight: 3,
                  padding: "2px 10px",
                  backgroundColor: (this.props.selectedComment && this.props.selectedComment.tid === c.tid) ? "rgb(240,240,240)" : "rgb(180,180,180)",
                  color: (this.props.selectedComment && this.props.selectedComment.tid === c.tid) ? "rgb(130,130,130)" : "rgb(255,255,255)",
                  borderRadius: 4,
                }}
                key={c.tid}>
                {c.tid}

            )})
          }

          <PaginateButton paginate={this.movePage(1)}>
            <g transform="translate(-13.000000, -5.000000)">
              <g transform="translate(18.000000, 10.000000) scale(-1, 1) translate(-18.000000, -10.000000) translate(13.000000, 5.000000)">
                <polyline fill="rgb(180,180,180)" points="-3.061617e-16 5 5 10 5 6.123234e-16"></polyline>
              </g>
            </g>
          </PaginateButton>
          <PaginateButton paginate={this.movePage(10)}>
            <g transform="translate(-13.000000, -5.000000)">
              <g transform="translate(18.000000, 10.000000) scale(-1, 1) translate(-18.000000, -10.000000) translate(13.000000, 5.000000)">
                <polyline fill="rgb(180,180,180)" points="4.50549451 5 9.50549451 10 9.50549451 6.123234e-16"></polyline>
                <polyline fill="rgb(180,180,180)" points="-3.061617e-16 5 5 10 5 6.123234e-16"></polyline>
              </g>
            </g>
          </PaginateButton>

    )
  }
}

export default TidCarousel;
<|endoftext|>


<commit_before>import {
  isIE9,
  isAndroid,
  toNumber,
  _toString,
  nextTick,
  debounce as _debounce
} from '../../../util/index'

export default {

  bind () {
    var self = this
    var el = this.el
    var isRange = el.type === 'range'
    var lazy = this.params.lazy
    var number = this.params.number
    var debounce = this.params.debounce

    // handle composition events.
    //   http://blog.evanyou.me/2014/01/03/composition-event/
    // skip this for Android because it handles composition
    // events quite differently. Android doesn't trigger
    // composition events for language input methods e.g.
    // Chinese, but instead triggers them for spelling
    // suggestions... (see Discussion/#162)
    var composing = false
    if (!isAndroid && !isRange) {
      this.on('compositionstart', function () {
        composing = true
      })
      this.on('compositionend', function () {
        composing = false
        // in IE11 the "compositionend" event fires AFTER
        // the "input" event, so the input handler is blocked
        // at the end... have to call it here.
        //
        // #1327: in lazy mode this is unecessary.
        if (!lazy) {
          self.listener()
        }
      })
    }

    // prevent messing with the input when user is typing,
    // and force update on blur.
    this.focused = false
    if (!isRange && !lazy) {
      this.on('focus', function () {
        self.focused = true
      })
      this.on('blur', function () {
        self.focused = false
        // do not sync value after fragment removal (#2017)
        if (!self._frag || self._frag.inserted) {
          self.rawListener()
        }
      })
    }

    // Now attach the main listener
    this.listener = this.rawListener = function () {
      if (composing || !self._bound) {
        return
      }
      var val = number || isRange
        ? toNumber(el.value)
        : el.value
      self.set(val)
      // force update on next tick to avoid lock & same value
      // also only update when user is not typing
      nextTick(function () {
        if (self._bound && !self.focused) {
          self.update(self._watcher.value)
        }
      })
    }

    // apply debounce
    if (debounce) {
      this.listener = _debounce(this.listener, debounce)
    }

    // Support jQuery events, since jQuery.trigger() doesn't
    // trigger native events in some cases and some plugins
    // rely on $.trigger()
    //
    // We want to make sure if a listener is attached using
    // jQuery, it is also removed with jQuery, that's why
    // we do the check for each directive instance and
    // store that check result on itself. This also allows
    // easier test coverage control by unsetting the global
    // jQuery variable in tests.
    this.hasjQuery = typeof jQuery === 'function'
    if (this.hasjQuery) {
      jQuery(el).on('change', this.listener)
      if (!lazy) {
        jQuery(el).on('input', this.listener)
      }
    } else {
      this.on('change', this.listener)
      if (!lazy) {
        this.on('input', this.listener)
      }
    }

    // IE9 doesn't fire input event on backspace/del/cut
    if (!lazy && isIE9) {
      this.on('cut', function () {
        nextTick(self.listener)
      })
      this.on('keyup', function (e) {
        if (e.keyCode === 46 || e.keyCode === 8) {
          self.listener()
        }
      })
    }

    // set initial value if present
    if (
      el.hasAttribute('value') ||
      (el.tagName === 'TEXTAREA' && el.value.trim())
    ) {
      this.afterBind = this.listener
    }
  },

  update (value) {
    this.el.value = _toString(value)
  },

  unbind () {
    var el = this.el
    if (this.hasjQuery) {
      jQuery(el).off('change', this.listener)
      jQuery(el).off('input', this.listener)
    }
  }
}
<commit_msg>fix jQuery < 1.7 compatibility (close #2199)<commit_after>import {
  isIE9,
  isAndroid,
  toNumber,
  _toString,
  nextTick,
  debounce as _debounce
} from '../../../util/index'

export default {

  bind () {
    var self = this
    var el = this.el
    var isRange = el.type === 'range'
    var lazy = this.params.lazy
    var number = this.params.number
    var debounce = this.params.debounce

    // handle composition events.
    //   http://blog.evanyou.me/2014/01/03/composition-event/
    // skip this for Android because it handles composition
    // events quite differently. Android doesn't trigger
    // composition events for language input methods e.g.
    // Chinese, but instead triggers them for spelling
    // suggestions... (see Discussion/#162)
    var composing = false
    if (!isAndroid && !isRange) {
      this.on('compositionstart', function () {
        composing = true
      })
      this.on('compositionend', function () {
        composing = false
        // in IE11 the "compositionend" event fires AFTER
        // the "input" event, so the input handler is blocked
        // at the end... have to call it here.
        //
        // #1327: in lazy mode this is unecessary.
        if (!lazy) {
          self.listener()
        }
      })
    }

    // prevent messing with the input when user is typing,
    // and force update on blur.
    this.focused = false
    if (!isRange && !lazy) {
      this.on('focus', function () {
        self.focused = true
      })
      this.on('blur', function () {
        self.focused = false
        // do not sync value after fragment removal (#2017)
        if (!self._frag || self._frag.inserted) {
          self.rawListener()
        }
      })
    }

    // Now attach the main listener
    this.listener = this.rawListener = function () {
      if (composing || !self._bound) {
        return
      }
      var val = number || isRange
        ? toNumber(el.value)
        : el.value
      self.set(val)
      // force update on next tick to avoid lock & same value
      // also only update when user is not typing
      nextTick(function () {
        if (self._bound && !self.focused) {
          self.update(self._watcher.value)
        }
      })
    }

    // apply debounce
    if (debounce) {
      this.listener = _debounce(this.listener, debounce)
    }

    // Support jQuery events, since jQuery.trigger() doesn't
    // trigger native events in some cases and some plugins
    // rely on $.trigger()
    //
    // We want to make sure if a listener is attached using
    // jQuery, it is also removed with jQuery, that's why
    // we do the check for each directive instance and
    // store that check result on itself. This also allows
    // easier test coverage control by unsetting the global
    // jQuery variable in tests.
    this.hasjQuery = typeof jQuery === 'function'
    if (this.hasjQuery) {
      const method = jQuery.fn.on ? 'on' : 'bind'
      jQuery(el)[method]('change', this.listener)
      if (!lazy) {
        jQuery(el)[method]('input', this.listener)
      }
    } else {
      this.on('change', this.listener)
      if (!lazy) {
        this.on('input', this.listener)
      }
    }

    // IE9 doesn't fire input event on backspace/del/cut
    if (!lazy && isIE9) {
      this.on('cut', function () {
        nextTick(self.listener)
      })
      this.on('keyup', function (e) {
        if (e.keyCode === 46 || e.keyCode === 8) {
          self.listener()
        }
      })
    }

    // set initial value if present
    if (
      el.hasAttribute('value') ||
      (el.tagName === 'TEXTAREA' && el.value.trim())
    ) {
      this.afterBind = this.listener
    }
  },

  update (value) {
    this.el.value = _toString(value)
  },

  unbind () {
    var el = this.el
    if (this.hasjQuery) {
      const method = jQuery.fn.off ? 'off' : 'unbind'
      jQuery(el)[method]('change', this.listener)
      jQuery(el)[method]('input', this.listener)
    }
  }
}
<|endoftext|>