<commit_before>/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        L = require('leaflet'),
        NtbModel = require('models/ntb'),
        PopoverTemplate = require('text!templates/pois/popover.html');

    // Module
    return NtbModel.extend({

        idAttribute: '_id',
        type: 'poi',
        changed: false,
        deleted: false,
        popoverTemplate: PopoverTemplate,
        forcedLicense: 'CC BY 4.0',

        urlRoot: function () {
            return '/restProxy/steder';
        },

        serverAttrs: [
            '_id',
            'beskrivelse',
            'bilder',
            'checksum',
            'endret',
            'fasiliteter',
            'fylke',
            'grupper',
            'geojson',
            'kommune',
            'lenker',
            'lisens',
            'navn',
            'navn_alt',
            'navngiving',
            'områder',
            'privat',
            'status',
            'ssr_id',
            'tags',
            'tilbyder',
            'tilrettelagt_for'
        ],

        defaults: {
            navn: '',
            lenker: [],
            lisens: 'CC BY-NC 4.0',
            status: 'Kladd',
            tags: [],
            markerIcon: '21'
        },

        validation: {
            navn: {
                required: true,
                msg: 'Dette feltet er påkrevd.'
            },
            kategori: function (val) {
                if (!val) {
                    return 'Dette feltet er påkrevd.';
                }
            },
            beskrivelse:  {
                required: true,
                msg: 'Dette feltet er påkrevd.'
            }
        },

        availableCategories: [
            {name: 'Hytte', markerIcon: '22'},
            {name: 'Fjelltopp', markerIcon: '21'},
            {name: 'Gapahuk', markerIcon: '33'},
            {name: 'Rasteplass', markerIcon: '30'},
            {name: 'Teltplass', markerIcon: '32'},
            {name: 'Geocaching', markerIcon: undefined},
            {name: 'Turpostkasse', markerIcon: undefined},
            {name: 'Turorientering', markerIcon: undefined},
            {name: 'Utsiktspunkt', markerIcon: '25'},
            {name: 'Attraksjon', markerIcon: '35'},
            {name: 'Badeplass', markerIcon: '26'},
            {name: 'Fiskeplass', markerIcon: '27'},
            {name: 'Klatrefelt', markerIcon: '29'},
            {name: 'Grotte', markerIcon: '35'},
            {name: 'Akebakke', markerIcon: '38'},
            {name: 'Skitrekk', markerIcon: undefined},
            {name: 'Kitested', markerIcon: '40'},
            {name: 'Skøytevann', markerIcon: undefined},
            {name: 'Toalett', markerIcon: undefined},
            {name: 'Bro', markerIcon: undefined},
            {name: 'Vadested', markerIcon: undefined},
            {name: 'Parkering', markerIcon: undefined},
            {name: 'Holdeplass', markerIcon: undefined},
            {name: 'Togstasjon', markerIcon: undefined}
        ],

        initialize: function (options) {

            this.on('change', function () {
                this.changed = true;
            });

            // this.on('change:navn', this.onNameChange, this);
            this.on('change:kategori', this.onCategoryChange, this);
            this.on('change:geojson', this.onGeoJsonChange, this);
            this.on('change:navn', this.onNameChange, this);

            var tags = this.get('tags');
            if (tags.length > 0) {
                this.set('kategori', tags[0]);
            }

            this.on('change:kategori', function () {
                var tags = _.clone(this.get('tags')) || [];
                tags[0] = this.get('kategori');
                this.set('tags', tags);
            });

            NtbModel.prototype.initialize.call(this, options);

        },

        onNameChange: function (e) {
            this.unset('ssr_id');
        },

        onCategoryChange: function (e) {
            var categoryName = this.get('kategori'),
                category = _.findWhere(this.availableCategories, {name: categoryName}),
                markerIcon;

            if (!!category && category['markerIcon']) {
                markerIcon = category['markerIcon'];

            } else if (!!this.defaults && this.defaults.markerIcon) {
                markerIcon = this.defaults.markerIcon;
            }

            this.set('markerIcon', markerIcon);

            // NOTE: Consider moving this to an onMarkerIconChange method.
            if (!!this.marker) {
                if (!!category) {
                    this.setMarkerIcon();
                }
            }
        },

        onGeoJsonChange: function (model, value, options) {
            if (this.hasMarker()) {
                try {
                    var latLng = L.latLng(this.getLatLng());
                    this.marker.setLatLng(latLng);
                } catch (e) {
                    console.error('Could not create L.latLng');
                }
            }

            this.event_aggregator.trigger('poi:geoJsonChange', this);
            this.unset('ssr_id');

        },

        hasChanged: function () {
            return !!this.changed;
        },

        resetHasChanged: function () {
            this.changed = false;
        },

        isDeleted: function () {
            return !!this.get('deleted') && this.get('deleted');
        },

        getGeoJson: function () {
            return _.clone(this.get('geojson'));
        },

        getMarker: function () {
            return this.marker;
        },

        hasMarker: function () {
            return !!this.marker;
        },

        setMarkerIcon: function () {
            var iconName = this.get('markerIcon');

            var icon = L.icon({
                iconUrl: '/images/markers/' + iconName + '.png',
                iconRetinaUrl: '/images/markers/' + iconName + '@2x.png',
                iconSize: [26, 32],
                iconAnchor: [13, 32],
                popupAnchor: [-0, -30]
            });

            try {
                this.marker.setIcon(icon);
            }
            catch(err) {
                console.error('Could not set marker icon', err);
            }
        },

        hasPosition: function () {
            var geojson = this.get('geojson');
            return !!geojson && !!geojson.coordinates;
        },

        // deletePoi: function () {
        //     this.set('deleted', true);
        //     this.trigger('deletePoi');
        // },

        updateGeojson: function (lat, lng) {
            var geoJson = this.getGeoJson();
            geoJson.coordinates = [lng, lat];
            this.set('geojson', geoJson);
        }

    });

});
<commit_msg>Update fylke, kommune and områder on poi geojson change<commit_after>/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        L = require('leaflet'),
        NtbModel = require('models/ntb'),
        PopoverTemplate = require('text!templates/pois/popover.html');

    // Module
    return NtbModel.extend({

        idAttribute: '_id',
        type: 'poi',
        changed: false,
        deleted: false,
        popoverTemplate: PopoverTemplate,
        forcedLicense: 'CC BY 4.0',

        urlRoot: function () {
            return '/restProxy/steder';
        },

        serverAttrs: [
            '_id',
            'beskrivelse',
            'bilder',
            'checksum',
            'endret',
            'fasiliteter',
            'fylke',
            'grupper',
            'geojson',
            'kommune',
            'lenker',
            'lisens',
            'navn',
            'navn_alt',
            'navngiving',
            'områder',
            'privat',
            'status',
            'ssr_id',
            'tags',
            'tilbyder',
            'tilrettelagt_for'
        ],

        defaults: {
            navn: '',
            lenker: [],
            lisens: 'CC BY-NC 4.0',
            status: 'Kladd',
            tags: [],
            markerIcon: '21'
        },

        validation: {
            navn: {
                required: true,
                msg: 'Dette feltet er påkrevd.'
            },
            kategori: function (val) {
                if (!val) {
                    return 'Dette feltet er påkrevd.';
                }
            },
            beskrivelse:  {
                required: true,
                msg: 'Dette feltet er påkrevd.'
            }
        },

        availableCategories: [
            {name: 'Hytte', markerIcon: '22'},
            {name: 'Fjelltopp', markerIcon: '21'},
            {name: 'Gapahuk', markerIcon: '33'},
            {name: 'Rasteplass', markerIcon: '30'},
            {name: 'Teltplass', markerIcon: '32'},
            {name: 'Geocaching', markerIcon: undefined},
            {name: 'Turpostkasse', markerIcon: undefined},
            {name: 'Turorientering', markerIcon: undefined},
            {name: 'Utsiktspunkt', markerIcon: '25'},
            {name: 'Attraksjon', markerIcon: '35'},
            {name: 'Badeplass', markerIcon: '26'},
            {name: 'Fiskeplass', markerIcon: '27'},
            {name: 'Klatrefelt', markerIcon: '29'},
            {name: 'Grotte', markerIcon: '35'},
            {name: 'Akebakke', markerIcon: '38'},
            {name: 'Skitrekk', markerIcon: undefined},
            {name: 'Kitested', markerIcon: '40'},
            {name: 'Skøytevann', markerIcon: undefined},
            {name: 'Toalett', markerIcon: undefined},
            {name: 'Bro', markerIcon: undefined},
            {name: 'Vadested', markerIcon: undefined},
            {name: 'Parkering', markerIcon: undefined},
            {name: 'Holdeplass', markerIcon: undefined},
            {name: 'Togstasjon', markerIcon: undefined}
        ],

        initialize: function (options) {

            this.on('change', function () {
                this.changed = true;
            });

            // this.on('change:navn', this.onNameChange, this);
            this.on('change:kategori', this.onCategoryChange, this);
            this.on('change:geojson', this.onGeoJsonChange, this);
            this.on('change:navn', this.onNameChange, this);
            this.on('change:geojson', this.updateBoundaryIntersect, this);

            var tags = this.get('tags');
            if (tags.length > 0) {
                this.set('kategori', tags[0]);
            }

            this.on('change:kategori', function () {
                var tags = _.clone(this.get('tags')) || [];
                tags[0] = this.get('kategori');
                this.set('tags', tags);
            });

            NtbModel.prototype.initialize.call(this, options);

        },

        onNameChange: function (e) {
            this.unset('ssr_id');
        },

        onCategoryChange: function (e) {
            var categoryName = this.get('kategori'),
                category = _.findWhere(this.availableCategories, {name: categoryName}),
                markerIcon;

            if (!!category && category['markerIcon']) {
                markerIcon = category['markerIcon'];

            } else if (!!this.defaults && this.defaults.markerIcon) {
                markerIcon = this.defaults.markerIcon;
            }

            this.set('markerIcon', markerIcon);

            // NOTE: Consider moving this to an onMarkerIconChange method.
            if (!!this.marker) {
                if (!!category) {
                    this.setMarkerIcon();
                }
            }
        },

        onGeoJsonChange: function (model, value, options) {
            if (this.hasMarker()) {
                try {
                    var latLng = L.latLng(this.getLatLng());
                    this.marker.setLatLng(latLng);
                } catch (e) {
                    console.error('Could not create L.latLng');
                }
            }

            this.event_aggregator.trigger('poi:geoJsonChange', this);
            this.unset('ssr_id');

        },

        hasChanged: function () {
            return !!this.changed;
        },

        resetHasChanged: function () {
            this.changed = false;
        },

        isDeleted: function () {
            return !!this.get('deleted') && this.get('deleted');
        },

        getGeoJson: function () {
            return _.clone(this.get('geojson'));
        },

        getMarker: function () {
            return this.marker;
        },

        hasMarker: function () {
            return !!this.marker;
        },

        setMarkerIcon: function () {
            var iconName = this.get('markerIcon');

            var icon = L.icon({
                iconUrl: '/images/markers/' + iconName + '.png',
                iconRetinaUrl: '/images/markers/' + iconName + '@2x.png',
                iconSize: [26, 32],
                iconAnchor: [13, 32],
                popupAnchor: [-0, -30]
            });

            try {
                this.marker.setIcon(icon);
            }
            catch(err) {
                console.error('Could not set marker icon', err);
            }
        },

        hasPosition: function () {
            var geojson = this.get('geojson');
            return !!geojson && !!geojson.coordinates;
        },

        // deletePoi: function () {
        //     this.set('deleted', true);
        //     this.trigger('deletePoi');
        // },

        updateGeojson: function (lat, lng) {
            var geoJson = this.getGeoJson();
            geoJson.coordinates = [lng, lat];
            this.set('geojson', geoJson);
        },

        updateBoundaryIntersect: function () {
            if (this.hasPosition()) {
                var geojson = this.get('geojson');
                $.ajax({
                    type: 'POST',
                    contentType: 'application/json',
                    url: 'http://geoserver2.dotcloudapp.com/api/v1/boundary/intersect',
                    data: JSON.stringify({geojson: geojson}),
                    success: $.proxy(this.setBoundaryIntersect, this)
                });

            } else {
                this.setBoundaryIntersect({fylke: undefined, kommune: undefined, 'områder': []});
            }
        },

        setBoundaryIntersect: function (data) {
            this.set('fylke', data['fylker'][0]);
            this.set('kommune', data['kommuner'][0]);
            this.set('områder', data['områder']);
        }

    });

});
<|endoftext|>


<commit_before>'use strict';
exports.info = {
  name: 'hat',
  desc: 'Composite a hat onto your avatar image',
  usage: 'hat <name> [Xoffset] [Yoffset] [scale] [rotation]',
};

const config = require('../../config');
const fs = require('fs');
const path = require('path');
const gm = require('gm');
const fetch = require('node-fetch');
const logger = require('winston');

// Downloads users avatar and  composites a selectable hat over it then send it back as a PM
exports.run = (discord, msg, params = []) => {
  const avatarUri = msg.author.avatarURL;
  if (!avatarUri) {
    msg.reply('Cannot perform this operation without a custom avatar.');
    return;
  }
  params.forEach(x => x.toLowerCase());
  if (!params[0] || params[0] === 'help') {
    if (msg.channel.type === 'text') {
      msg.channel.sendMessage(':mailbox_with_mail:');
    }
    sendHatFormatMessage();
    return;
  }

  if (msg.channel.type === 'text') {
    msg.channel.sendMessage(':mailbox_with_mail:');
    sendHatFormatMessage();
  }

  const translateParams = processParams(params);
  if (!translateParams) {
    msg.author.sendMessage(`Format error. Type \`\`${config.commands.prefix}hat\`\` to see usage options.`);
    return;
  }

  const hatFile = path.join(__dirname, '../../assets/hats/', `${params[0]}.png`);

  fs.exists(hatFile, (exists) => {
    if (!exists) {
      msg.author.sendMessage(`'${params[0]}' is not a registered hat.`);
      return;
    }
    const hatData = {
      avatarUri,
      hatFile: hatFile,
      x: translateParams.x,
      y: translateParams.y,
      s: translateParams.s,
      r: translateParams.r,
    };
    msg.author.sendMessage('Processing...')
      .then(m => {
        loadHat(hatData)
          .then(getHatSize)
          .then(downloadAvatar)
          .then(modifyHat)
          .then(getModifiedHatSize)
          .then(saveModifiedHat)
          .then(applyHatComposite)
          .then(data => {
            m.edit(`Completed in ${m.createdTimestamp - msg.createdTimestamp}ms`);
            msg.author.sendFile(data.avatarBufferEdited, `${msg.author.id}_${params[0]}.png`)
              .then(() => {
                fs.unlink(data.tempHatPath);
              });
          })
          .catch((err) => {
            logger.error('Error processing hat:', err);
            m.edit(`Error processing your '${params[0]}' hat. Please try again...`);
          });
      });
  });

  function sendHatFormatMessage() {
    msg.author.sendMessage(`\`\`\`Examples:\n${config.commands.prefix}hat <name> ` +
      `[Xoffset] [Yoffset] [scale] [rotation]\n${config.commands.prefix}hat xmas 0 20 0.9 45\nHats: xmas hallo\`\`\``);
  }

  function processParams() {
    // Shift X
    let x = 0;
    // Shift Y
    let y = 0;
    // Scale
    let s = 0;
    // Rotation
    let r = 0;

    if (params[1]) {
      x = parseInt(params[1]);
    }
    if (params[2]) {
      y = -parseInt(params[2]);
    }
    if (params[3]) {
      // %
      if (params[3].indexOf('.') === -1) {
        s = parseInt(params[3]);
      } else {
        s = parseFloat(params[3]) * 100;
      }
    }
    if (params[4]) {
      r = parseInt(params[4]);
    }

    if (isNaN(x) || isNaN(y) || isNaN(s) || isNaN(r)) {
      return null;
    }

    return {
      x,
      y,
      s,
      r,
    };
  }

  function loadHat(hatData) {
    return new Promise((resolve, reject) => {
      fs.readFile(hatData.hatFile, (err, buffer) => {
        if (err) {
          reject(err);
        } else {
          hatData.hatBuffer = buffer;
          resolve(hatData);
        }
      });
    });
  }

  function getHatSize(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBuffer)
        .size((err, hatSize) => {
          if (err) {
            reject(err);
          } else {
            hatData.hatSize = hatSize;
            hatData.startX = (128 - hatSize.width) / 2;
            hatData.startY = (128 - hatSize.height) / 2;
            resolve(hatData);
          }
        });
    });
  }

  function downloadAvatar(hatData) {
    return new Promise((resolve, reject) => {
      fetch(encodeURI(hatData.avatarUri))
        .then(r => r.buffer())
        .then(buffer => {
          hatData.avatarBuffer = buffer;
          resolve(hatData);
        })
        .catch(reject);
    });
  }

  function modifyHat(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBuffer)
        .scale(`${hatData.s.toString()}%${hatData.s.toString()}%`)
        .rotate('#FFFF', hatData.r)
        .toBuffer('PNG', (err, hatBufferEdited) => {
          if (err) {
            reject(err);
          } else {
            hatData.hatBufferEdited = hatBufferEdited;
            resolve(hatData);
          }
        });
    });
  }

  function getModifiedHatSize(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBufferEdited)
        .size((err, hatModifiedSize) => {
          if (err) {
            reject(err);
          } else {
            const shiftX = (hatData.hatSize.width - hatModifiedSize.width) / 2;
            const shiftY = (hatData.hatSize.height - hatModifiedSize.height) / 2;
            hatData.endX = hatData.startX + shiftX + hatData.x;
            hatData.endY = hatData.startY + shiftY + hatData.y;
            resolve(hatData);
          }
        });
    });
  }

  function saveModifiedHat(hatData) {
    return new Promise((resolve, reject) => {
      const tempHatPath = path.join(__dirname, `../../temp/hat_${msg.author.id}_${params[0]}.png`);
      fs.writeFile(tempHatPath, hatData.hatBufferEdited, (err) => {
        if (err) {
          reject(err);
        } else {
          hatData.tempHatPath = tempHatPath;
          resolve(hatData);
        }
      });
    });
  }

  function applyHatComposite(hatData) {
    return new Promise((resolve, reject) => {
      const x = hatData.endX >= 0 ? `+${hatData.endX.toString()}` : hatData.endX.toString();
      const y = hatData.endY >= 0 ? `+${hatData.endY.toString()}` : hatData.endY.toString();
      gm(hatData.avatarBuffer)
        .composite(hatData.tempHatPath)
        .geometry(x + y)
        .toBuffer('PNG', (err, avatarBufferEdited) => {
          if (err) {
            reject(err);
          } else {
            hatData.avatarBufferEdited = avatarBufferEdited;
            resolve(hatData);
          }
        });
    });
  }
};
<commit_msg>Altered format for listing hat names<commit_after>'use strict';
exports.info = {
  name: 'hat',
  desc: 'Composite a hat onto your avatar image',
  usage: 'hat <name> [Xoffset] [Yoffset] [scale] [rotation]',
};

const config = require('../../config');
const fs = require('fs');
const path = require('path');
const gm = require('gm');
const fetch = require('node-fetch');
const logger = require('winston');

// Downloads users avatar and  composites a selectable hat over it then send it back as a PM
exports.run = (discord, msg, params = []) => {
  const avatarUri = msg.author.avatarURL;
  if (!avatarUri) {
    msg.reply('Cannot perform this operation without a custom avatar.');
    return;
  }
  params.forEach(x => x.toLowerCase());
  if (!params[0] || params[0] === 'help') {
    if (msg.channel.type === 'text') {
      msg.channel.sendMessage(':mailbox_with_mail:');
    }
    sendHatFormatMessage();
    return;
  }

  if (msg.channel.type === 'text') {
    msg.channel.sendMessage(':mailbox_with_mail:');
    sendHatFormatMessage();
  }

  const translateParams = processParams(params);
  if (!translateParams) {
    msg.author.sendMessage(`Format error. Type \`\`${config.commands.prefix}hat\`\` to see usage options.`);
    return;
  }

  const hatFile = path.join(__dirname, '../../assets/hats/', `${params[0]}.png`);

  fs.exists(hatFile, (exists) => {
    if (!exists) {
      msg.author.sendMessage(`'${params[0]}' is not a registered hat.`);
      return;
    }
    const hatData = {
      avatarUri,
      hatFile: hatFile,
      x: translateParams.x,
      y: translateParams.y,
      s: translateParams.s,
      r: translateParams.r,
    };
    msg.author.sendMessage('Processing...')
      .then(m => {
        loadHat(hatData)
          .then(getHatSize)
          .then(downloadAvatar)
          .then(modifyHat)
          .then(getModifiedHatSize)
          .then(saveModifiedHat)
          .then(applyHatComposite)
          .then(data => {
            m.edit(`Completed in ${m.createdTimestamp - msg.createdTimestamp}ms`);
            msg.author.sendFile(data.avatarBufferEdited, `${msg.author.id}_${params[0]}.png`)
              .then(() => {
                fs.unlink(data.tempHatPath);
              });
          })
          .catch((err) => {
            logger.error('Error processing hat:', err);
            m.edit(`Error processing your '${params[0]}' hat. Please try again...`);
          });
      });
  });

  function sendHatFormatMessage() {
    msg.author.sendMessage(`\`\`\`Examples:\n${config.commands.prefix}hat <name> ` +
      `[Xoffset] [Yoffset] [scale] [rotation]\n${config.commands.prefix}hat xmas 0 20 0.9 45\nHats: xmas | hallo\`\`\``);
  }

  function processParams() {
    // Shift X
    let x = 0;
    // Shift Y
    let y = 0;
    // Scale
    let s = 0;
    // Rotation
    let r = 0;

    if (params[1]) {
      x = parseInt(params[1]);
    }
    if (params[2]) {
      y = -parseInt(params[2]);
    }
    if (params[3]) {
      // %
      if (params[3].indexOf('.') === -1) {
        s = parseInt(params[3]);
      } else {
        s = parseFloat(params[3]) * 100;
      }
    }
    if (params[4]) {
      r = parseInt(params[4]);
    }

    if (isNaN(x) || isNaN(y) || isNaN(s) || isNaN(r)) {
      return null;
    }

    return {
      x,
      y,
      s,
      r,
    };
  }

  function loadHat(hatData) {
    return new Promise((resolve, reject) => {
      fs.readFile(hatData.hatFile, (err, buffer) => {
        if (err) {
          reject(err);
        } else {
          hatData.hatBuffer = buffer;
          resolve(hatData);
        }
      });
    });
  }

  function getHatSize(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBuffer)
        .size((err, hatSize) => {
          if (err) {
            reject(err);
          } else {
            hatData.hatSize = hatSize;
            hatData.startX = (128 - hatSize.width) / 2;
            hatData.startY = (128 - hatSize.height) / 2;
            resolve(hatData);
          }
        });
    });
  }

  function downloadAvatar(hatData) {
    return new Promise((resolve, reject) => {
      fetch(encodeURI(hatData.avatarUri))
        .then(r => r.buffer())
        .then(buffer => {
          hatData.avatarBuffer = buffer;
          resolve(hatData);
        })
        .catch(reject);
    });
  }

  function modifyHat(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBuffer)
        .scale(`${hatData.s.toString()}%${hatData.s.toString()}%`)
        .rotate('#FFFF', hatData.r)
        .toBuffer('PNG', (err, hatBufferEdited) => {
          if (err) {
            reject(err);
          } else {
            hatData.hatBufferEdited = hatBufferEdited;
            resolve(hatData);
          }
        });
    });
  }

  function getModifiedHatSize(hatData) {
    return new Promise((resolve, reject) => {
      gm(hatData.hatBufferEdited)
        .size((err, hatModifiedSize) => {
          if (err) {
            reject(err);
          } else {
            const shiftX = (hatData.hatSize.width - hatModifiedSize.width) / 2;
            const shiftY = (hatData.hatSize.height - hatModifiedSize.height) / 2;
            hatData.endX = hatData.startX + shiftX + hatData.x;
            hatData.endY = hatData.startY + shiftY + hatData.y;
            resolve(hatData);
          }
        });
    });
  }

  function saveModifiedHat(hatData) {
    return new Promise((resolve, reject) => {
      const tempHatPath = path.join(__dirname, `../../temp/hat_${msg.author.id}_${params[0]}.png`);
      fs.writeFile(tempHatPath, hatData.hatBufferEdited, (err) => {
        if (err) {
          reject(err);
        } else {
          hatData.tempHatPath = tempHatPath;
          resolve(hatData);
        }
      });
    });
  }

  function applyHatComposite(hatData) {
    return new Promise((resolve, reject) => {
      const x = hatData.endX >= 0 ? `+${hatData.endX.toString()}` : hatData.endX.toString();
      const y = hatData.endY >= 0 ? `+${hatData.endY.toString()}` : hatData.endY.toString();
      gm(hatData.avatarBuffer)
        .composite(hatData.tempHatPath)
        .geometry(x + y)
        .toBuffer('PNG', (err, avatarBufferEdited) => {
          if (err) {
            reject(err);
          } else {
            hatData.avatarBufferEdited = avatarBufferEdited;
            resolve(hatData);
          }
        });
    });
  }
};
<|endoftext|>


<commit_before>// Create a function that operates on a binary string and converts it to a decimal number

// You can't use JavaScript built-in parsing (parseInt and the like)
// Make the function available to be called on strings as a method
// Create a function called "assert_equal" that will determine if the 2 arguments passed to it are equal
// Use this assert_equal function to test your binaryToDecimal function
// assert_equal("11001010".binaryToDecimal(), 202);
// assert_equal("1110".binaryToDecimal(), 14);

function assert_equal(bin, dec) {

// binary string to decimal number

var binary;

	for(var i=0; i < bin.length - 1; i++) {
		binary = bin.charAt(i);

		console.log("binary: " + binary);
	}

}

// console.log(assert_equal("11001010".binaryToDecimal(), 202));
// console.log(assert_equal("1110".binaryToDecimal(), 14));<commit_msg>	modified:   BinarytoDecimal.js<commit_after>// Create a function that operates on a binary string and converts it to a decimal number

// You can't use JavaScript built-in parsing (parseInt and the like)
// Make the function available to be called on strings as a method
// Create a function called "assert_equal" that will determine if the 2 arguments passed to it are equal
// Use this assert_equal function to test your binaryToDecimal function
// assert_equal("11001010".binaryToDecimal(), 202);
// assert_equal("1110".binaryToDecimal(), 14);

function assert_equal(bin, dec) {

// binary string to decimal number

var obj = {};

var binary;

	for(var i=0; i < bin.length - 1; i++) {
		binary = bin.charAt(i);

		console.log("binary: " + binary);
	}

}

// console.log(assert_equal("11001010".binaryToDecimal(), 202));
// console.log(assert_equal("1110".binaryToDecimal(), 14));<|endoftext|>


<commit_before>var path = require('path'),
        fs = require('fs-extra'),
        async = require('async'),
        extend = require('extend'),
        multiGlob = require('multi-glob');

module.exports = {
    writeFiles: function(folder, files, locationMsg, doNotFreeMem, done) {
        if (typeof doNotFreeMem == 'function') {
            done = doNotFreeMem;
            doNotFreeMem = false;
        }
        var server = this;
        fs.mkdirs(folder, function(err) {
            if (err) {
                err = "error creating folder {0} on {1}\n{2}".format(folder, locationMsg, err);
                done(err);
            } else {
                if (files.length) {
                    async.each(files, function(file, cb) {
                        var basename = file.file.replace(/\\/g, '/').replace(/.*\//, '');
                        var fileName = path.resolve(folder, basename);
                        console.log(fileName);
                        file.file = fileName;
                        try {
                            var data = new Buffer(file.content.data, 'base64');
                            if (file.content) {
                                fs.writeFile(fileName, data, {
                                    encoding: 'binary'
                                }, function(err) {
                                    if (!doNotFreeMem) {
                                        delete file.content; //free server's memory with file's content
                                    }
                                    cb(err);
                                });
                            } else {
                                cb(null);
                            }
                        } catch (e) {
                            cb(e);
                        }
                    }, function(err) {
                        if (err) {
                            err = "error saving cordova build files to {0} on {1}\n{2}".format(folder, locationMsg, err);
                        }
                        done(err);
                    });
                } else {
                    done(null);
                }
            }
        });
    },
    freeMemFiles: function(files) {
        files.forEach(function(file) {
            delete file.content;
        });
    },
    readFiles: function(files, locationMsg, done) {
        if (files.length) {
            async.each(files, function(file, cb) {
                fs.readFile(file.file, {
                    //encoding: 'binary',
                }, function(err, data) {
                    if (!err) {
                        var buf = new Buffer(data);
                        file.content = {
                            data: buf.toString('base64')
                        };
                        var length = file.content.data.length;
                        global.bu = buf;
                    }
                    cb(err);
                });
            }, function(err) {
                if (err) {
                    err = "error reading build input files on {0}\n{1}".format(locationMsg, err);
                }
                done(err);
            });
        } else {
            done(null);
        }
    },
    cleanLastFolders: function(keepLast, globsArray, done) {
        if (keepLast <= 0) {
            done();
        }
        multiGlob.glob(globsArray, function(err, paths) {
            if (err) {
                return done(err);
            }
            if (!paths.length) {
                return done();
            }

            async.map(paths, function(path, done) {
                fs.stat(path, function(err, stat) {
                    done(err, {path: path, stat: stat});
                });
            }, function(err, stats) {
                if (err) {
                    return done(err);
                }
                stats = stats.filter(function(stat) {
                    return stat.stat.isDirectory();
                });
                stats.sort(function(a, b) {
                    return b.stat.mtime - a.stat.mtime;
                });
                stats.splice(0, keepLast);
                async.each(stats, function(stat, cb) {
                    fs.remove(stat.path, cb);
                }, function(err) {
                    done(err, stats);
                });
            });
        });
    }
};<commit_msg>remove console logging<commit_after>var path = require('path'),
        fs = require('fs-extra'),
        async = require('async'),
        extend = require('extend'),
        multiGlob = require('multi-glob');

module.exports = {
    writeFiles: function (folder, files, locationMsg, doNotFreeMem, done) {
        if (typeof doNotFreeMem == 'function') {
            done = doNotFreeMem;
            doNotFreeMem = false;
        }
        var server = this;
        fs.mkdirs(folder, function (err) {
            if (err) {
                err = "error creating folder {0} on {1}\n{2}".format(folder, locationMsg, err);
                done(err);
            } else {
                if (files.length) {
                    async.each(files, function (file, cb) {
                        var basename = file.file.replace(/\\/g, '/').replace(/.*\//, '');
                        var fileName = path.resolve(folder, basename);
                        file.file = fileName;
                        try {
                            var data = new Buffer(file.content.data, 'base64');
                            if (file.content) {
                                fs.writeFile(fileName, data, {
                                    encoding: 'binary'
                                }, function (err) {
                                    if (!doNotFreeMem) {
                                        delete file.content; //free server's memory with file's content
                                    }
                                    cb(err);
                                });
                            } else {
                                cb(null);
                            }
                        } catch (e) {
                            cb(e);
                        }
                    }, function (err) {
                        if (err) {
                            err = "error saving cordova build files to {0} on {1}\n{2}".format(folder, locationMsg, err);
                        }
                        done(err);
                    });
                } else {
                    done(null);
                }
            }
        });
    },
    freeMemFiles: function (files) {
        files.forEach(function (file) {
            delete file.content;
        });
    },
    readFiles: function (files, locationMsg, done) {
        if (files.length) {
            async.each(files, function (file, cb) {
                fs.readFile(file.file, {
                    //encoding: 'binary',
                }, function (err, data) {
                    if (!err) {
                        var buf = new Buffer(data);
                        file.content = {
                            data: buf.toString('base64')
                        };
                        var length = file.content.data.length;
                        global.bu = buf;
                    }
                    cb(err);
                });
            }, function (err) {
                if (err) {
                    err = "error reading build input files on {0}\n{1}".format(locationMsg, err);
                }
                done(err);
            });
        } else {
            done(null);
        }
    },
    cleanLastFolders: function (keepLast, globsArray, done) {
        if (keepLast <= 0) {
            done();
        }
        multiGlob.glob(globsArray, function (err, paths) {
            if (err) {
                return done(err);
            }
            if (!paths.length) {
                return done();
            }

            async.map(paths, function (path, done) {
                fs.stat(path, function (err, stat) {
                    done(err, {path: path, stat: stat});
                });
            }, function (err, stats) {
                if (err) {
                    return done(err);
                }
                stats = stats.filter(function (stat) {
                    return stat.stat.isDirectory();
                });
                stats.sort(function (a, b) {
                    return b.stat.mtime - a.stat.mtime;
                });
                stats.splice(0, keepLast);
                async.each(stats, function (stat, cb) {
                    fs.remove(stat.path, cb);
                }, function (err) {
                    done(err, stats);
                });
            });
        });
    }
};<|endoftext|>


<commit_before>openerp.mass_mailing = function (instance) {
    var _t = instance.web._t;
	debugger;

    openerp.mass_mailing = function(openerp) {
        openerp.web_kanban.KanbanRecord.include({
            on_card_clicked: function (event) {
                if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
                    this.$('.oe_mailings').click();
                } else {
                    this._super.apply(this, arguments);
                }
            },
        });
    };

    instance.web.form.CharDomainButton = instance.web.form.AbstractField.extend({
        template: 'CharDomainButton',
        init: function(field_manager, node) {
            this._super.apply(this, arguments);
        },
        start: function() {
            var self=this;
            this._super.apply(this, arguments);
            $('button', this.$el).on('click', self.on_click);
            this.set_button();
        },
        set_button: function() {
            if (this.get('value')) {
                // TODO: rpc to copute X
                $('.oe_domain_count', this.$el).text('X records selected');
                $('button span', this.$el).text(' Change selection');
            } else {
                $('.oe_domain_count', this.$el).text('0 record selected');
                $('button span', this.$el).text(' Select records');
            };
        },
        on_click: function(ev) {
            var self = this;
            var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
            this.pop = new instance.web.form.SelectCreatePopup(this);
            this.pop.select_element(
                model, {title: 'Select records...'},
                [], this.build_context());
            this.pop.on("elements_selected", self, function() {
                var self2 = this;
                var search_data = this.pop.searchview.build_search_data()
                instance.web.pyeval.eval_domains_and_contexts({
                    domains: search_data.domains,
                    contexts: search_data.contexts,
                    group_by_seq: search_data.groupbys || []
                }).then(function (results) {
                    var domain = self2.pop.dataset.domain.concat(results.domain || []);
                    self.set_value(JSON.stringify(domain))
                });
            });
            event.preventDefault();
        },
        set_value: function(value_) {
            var self = this;
            this.set('value', value_ || false);
            this.set_button();
         },
    });

    instance.web.form.widgets = instance.web.form.widgets.extend(
        {'char_domain': 'instance.web.form.CharDomainButton'}
    );

};
<commit_msg>[IMP] commment<commit_after>openerp.mass_mailing = function (instance) {
    var _t = instance.web._t;
    openerp.mass_mailing = function(openerp) {
        openerp.web_kanban.KanbanRecord.include({
            on_card_clicked: function (event) {
                if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
                    this.$('.oe_mailings').click();
                } else {
                    this._super.apply(this, arguments);
                }
            },
        });
    };

    instance.web.form.CharDomainButton = instance.web.form.AbstractField.extend({
        template: 'CharDomainButton',
        init: function(field_manager, node) {
            this._super.apply(this, arguments);
        },
        start: function() {
            var self=this;
            this._super.apply(this, arguments);
            $('button', this.$el).on('click', self.on_click);
            this.set_button();
        },
        set_button: function() {
            if (this.get('value')) {
                // TODO: rpc to copute X
                $('.oe_domain_count', this.$el).text('X records selected');
                $('button span', this.$el).text(' Change selection');
            } else {
                $('.oe_domain_count', this.$el).text('0 record selected');
                $('button span', this.$el).text(' Select records');
            };
        },
        on_click: function(ev) {
            var self = this;
            var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
            this.pop = new instance.web.form.SelectCreatePopup(this);
            this.pop.select_element(
                model, {title: 'Select records...'},
                [], this.build_context());
            this.pop.on("elements_selected", self, function() {
                var self2 = this;
                var search_data = this.pop.searchview.build_search_data()
                instance.web.pyeval.eval_domains_and_contexts({
                    domains: search_data.domains,
                    contexts: search_data.contexts,
                    group_by_seq: search_data.groupbys || []
                }).then(function (results) {
                    // if selected IDS change domain
                    var domain = self2.pop.dataset.domain.concat(results.domain || []);
                    self.set_value(JSON.stringify(domain))
                });
            });
            event.preventDefault();
        },
        set_value: function(value_) {
            var self = this;
            this.set('value', value_ || false);
            this.set_button();
         },
    });

    instance.web.form.widgets = instance.web.form.widgets.extend(
        {'char_domain': 'instance.web.form.CharDomainButton'}
    );

};
<|endoftext|>


<commit_before>// Trunk8
(function ($) { var methods, utils, SIDES = { center: 'center', left: 'left', right: 'right' }, WIDTH = { auto: 'auto' }; function trunk8(element) { this.$element = $(element); this.original_text = this.$element.html(); this.settings = $.extend({}, $.fn.trunk8.defaults); } trunk8.prototype.updateSettings = function (options) { this.settings = $.extend(this.settings, options); }; function truncate() { var data = this.data('trunk8'), settings = data.settings, width = settings.width, side = settings.side, fill = settings.fill, line_height = utils.getLineHeight(this) * settings.lines, str = data.original_text, length = str.length, max_bite = '', lower, upper, bite_size, bite; this.html(str); if (width === WIDTH.auto) { if (this.height() <= line_height) { return; } lower = 0; upper = length - 1; while (lower <= upper) { bite_size = lower + ((upper - lower) >> 1); bite = utils.eatStr(str, side, length - bite_size, fill); this.html(bite); if (this.height() > line_height) { upper = bite_size - 1; } else { lower = bite_size + 1; max_bite = (max_bite.length > bite.length) ? max_bite : bite; } } this.html(''); this.html(max_bite); if (settings.tooltip) { this.attr('title', str); } } else if (!isNaN(width)) { bite_size = length - width; bite = utils.eatStr(str, side, bite_size, fill); this.html(bite); if (settings.tooltip) { this.attr('title', str); } } else { $.error('Invalid width "' + width + '".'); } } methods = { init: function (options) { return this.each(function () { var $this = $(this), data = $this.data('trunk8'); if (!data) { $this.data('trunk8', (data = new trunk8(this))); } data.updateSettings(options); truncate.call($this); }); }, update: function (new_string) { return this.each(function () { var $this = $(this); if (new_string) { $this.data('trunk8').original_text = new_string; } truncate.call($this); }); }, revert: function () { return this.each(function () { var text = $(this).data('trunk8').original_text; $(this).html(text); }); }, getSettings: function () { return this.get(0).data('trunk8').settings; } }; utils = { eatStr: function (str, side, bite_size, fill) { var length = str.length, key = utils.eatStr.generateKey.apply(null, arguments), half_length, half_bite_size; if (utils.eatStr.cache[key]) { return utils.eatStr.cache[key]; } if ((typeof str !== 'string') || (length === 0)) { $.error('Invalid source string "' + str + '".'); } if ((bite_size < 0) || (bite_size > length)) { $.error('Invalid bite size "' + bite_size + '".'); } else if (bite_size === 0) { return str; } if (typeof (fill + '') !== 'string') { $.error('Fill unable to be converted to a string.'); } switch (side) { case SIDES.right: return utils.eatStr.cache[key] = $.trim(str.substr(0, length - bite_size)) + fill; case SIDES.left: return utils.eatStr.cache[key] = fill + $.trim(str.substr(bite_size)); case SIDES.center: half_length = length >> 1; half_bite_size = bite_size >> 1; return utils.eatStr.cache[key] = $.trim(utils.eatStr(str.substr(0, length - half_length), SIDES.right, bite_size - half_bite_size, '')) + fill + $.trim(utils.eatStr(str.substr(length - half_length), SIDES.left, half_bite_size, '')); default: $.error('Invalid side "' + side + '".'); } }, getLineHeight: function (elem) { var $elem = $(elem), float = $elem.css('float'), position = $elem.css('position'), html = $elem.html(), wrapper_id = 'line-height-test', line_height; if (float !== 'none') { $elem.css('float', 'none'); } if (position === 'absolute') { $elem.css('position', 'static'); } $elem.html('i').wrap('
'); line_height = $('#' + wrapper_id).innerHeight(); $elem.html(html).css({ 'float': float, 'position': position }).unwrap(); return line_height; } }; utils.eatStr.cache = {}; utils.eatStr.generateKey = function () { return Array.prototype.join.call(arguments, ''); }; $.fn.trunk8 = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.trunk8'); } }; $.fn.trunk8.defaults = { fill: '…', lines: 1, side: SIDES.right, tooltip: true, width: WIDTH.auto }; })(jQuery);

function setPageTo(pageId, dataStuff) {
	$("html, body").animate({ scrollTop: 0 }, 125);

	setTimeout(function () {
		$(".page.showing").removeClass("showing");
		$("#" + pageId).addClass("showing");
	}, 150);
}

function shouldLoadWelcome(callback) {
	cpal.storage.getKey("showedMenubarWelcome", function (resp) {
		if (resp == undefined || resp == false) {
			callback(true);
		} else {
			callback(false);
		}
	});
}

$(document).ready(function () {
	shouldLoadWelcome(function (loadWelcome) {
		if (!loadWelcome) {
			window.coursesLib.checkLoggedIn(function (loginStatus) {
				if (!loginStatus.isLoggedIn) {
					setPageTo("notLoggedInPage");
					return;
				}
				window.coursesLib.getProfile(function (response) {
					window.coursesLib.getUpcomingEvents(function (response) {
						for (var i = 0; i < response.events.length; i++) {
							var thisEvent = response.events[i];
							var $eventItem = $('

');

							$eventItem.children(".upcoming-title").text(thisEvent.title);
							$eventItem.children(".upcoming-course").text(thisEvent.course);
							var descThing = thisEvent.normText;
							if (descThing.length > 150) {
								descThing = descThing.substring(0, 150);
								descThing += "...";
							}
							$eventItem.children(".upcoming-desc").text(descThing);

							$("#upcomingEventList").append($eventItem);
							/*$(".upcoming-desc").each(function () {
								//$(this).text().sp
							});*/
						}
						setPageTo("mainPage");
					});
				});
			});
		} else {
			setPageTo("welcomePage");
		}
	});

	$("#soundsGood").click(function () {
		cpal.storage.setKey("showedMenubarWelcome", true, function () {
			window.location.reload();
		});
	});
	$("#noThanks").click(function () {
		chrome.tabs.create({
			url: cpal.resources.getURL("etc/disablemenu.html")
		});
	});
});<commit_msg>Add "see details" link to menu bar popup<commit_after>// Trunk8
(function ($) { var methods, utils, SIDES = { center: 'center', left: 'left', right: 'right' }, WIDTH = { auto: 'auto' }; function trunk8(element) { this.$element = $(element); this.original_text = this.$element.html(); this.settings = $.extend({}, $.fn.trunk8.defaults); } trunk8.prototype.updateSettings = function (options) { this.settings = $.extend(this.settings, options); }; function truncate() { var data = this.data('trunk8'), settings = data.settings, width = settings.width, side = settings.side, fill = settings.fill, line_height = utils.getLineHeight(this) * settings.lines, str = data.original_text, length = str.length, max_bite = '', lower, upper, bite_size, bite; this.html(str); if (width === WIDTH.auto) { if (this.height() <= line_height) { return; } lower = 0; upper = length - 1; while (lower <= upper) { bite_size = lower + ((upper - lower) >> 1); bite = utils.eatStr(str, side, length - bite_size, fill); this.html(bite); if (this.height() > line_height) { upper = bite_size - 1; } else { lower = bite_size + 1; max_bite = (max_bite.length > bite.length) ? max_bite : bite; } } this.html(''); this.html(max_bite); if (settings.tooltip) { this.attr('title', str); } } else if (!isNaN(width)) { bite_size = length - width; bite = utils.eatStr(str, side, bite_size, fill); this.html(bite); if (settings.tooltip) { this.attr('title', str); } } else { $.error('Invalid width "' + width + '".'); } } methods = { init: function (options) { return this.each(function () { var $this = $(this), data = $this.data('trunk8'); if (!data) { $this.data('trunk8', (data = new trunk8(this))); } data.updateSettings(options); truncate.call($this); }); }, update: function (new_string) { return this.each(function () { var $this = $(this); if (new_string) { $this.data('trunk8').original_text = new_string; } truncate.call($this); }); }, revert: function () { return this.each(function () { var text = $(this).data('trunk8').original_text; $(this).html(text); }); }, getSettings: function () { return this.get(0).data('trunk8').settings; } }; utils = { eatStr: function (str, side, bite_size, fill) { var length = str.length, key = utils.eatStr.generateKey.apply(null, arguments), half_length, half_bite_size; if (utils.eatStr.cache[key]) { return utils.eatStr.cache[key]; } if ((typeof str !== 'string') || (length === 0)) { $.error('Invalid source string "' + str + '".'); } if ((bite_size < 0) || (bite_size > length)) { $.error('Invalid bite size "' + bite_size + '".'); } else if (bite_size === 0) { return str; } if (typeof (fill + '') !== 'string') { $.error('Fill unable to be converted to a string.'); } switch (side) { case SIDES.right: return utils.eatStr.cache[key] = $.trim(str.substr(0, length - bite_size)) + fill; case SIDES.left: return utils.eatStr.cache[key] = fill + $.trim(str.substr(bite_size)); case SIDES.center: half_length = length >> 1; half_bite_size = bite_size >> 1; return utils.eatStr.cache[key] = $.trim(utils.eatStr(str.substr(0, length - half_length), SIDES.right, bite_size - half_bite_size, '')) + fill + $.trim(utils.eatStr(str.substr(length - half_length), SIDES.left, half_bite_size, '')); default: $.error('Invalid side "' + side + '".'); } }, getLineHeight: function (elem) { var $elem = $(elem), float = $elem.css('float'), position = $elem.css('position'), html = $elem.html(), wrapper_id = 'line-height-test', line_height; if (float !== 'none') { $elem.css('float', 'none'); } if (position === 'absolute') { $elem.css('position', 'static'); } $elem.html('i').wrap('
'); line_height = $('#' + wrapper_id).innerHeight(); $elem.html(html).css({ 'float': float, 'position': position }).unwrap(); return line_height; } }; utils.eatStr.cache = {}; utils.eatStr.generateKey = function () { return Array.prototype.join.call(arguments, ''); }; $.fn.trunk8 = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.trunk8'); } }; $.fn.trunk8.defaults = { fill: '…', lines: 1, side: SIDES.right, tooltip: true, width: WIDTH.auto }; })(jQuery);

function setPageTo(pageId, dataStuff) {
	$("html, body").animate({ scrollTop: 0 }, 125);

	setTimeout(function () {
		$(".page.showing").removeClass("showing");
		$("#" + pageId).addClass("showing");
	}, 150);
}

function shouldLoadWelcome(callback) {
	cpal.storage.getKey("showedMenubarWelcome", function (resp) {
		if (resp == undefined || resp == false) {
			callback(true);
		} else {
			callback(false);
		}
	});
}

$(document).ready(function () {
	shouldLoadWelcome(function (loadWelcome) {
		if (!loadWelcome) {
			window.coursesLib.checkLoggedIn(function (loginStatus) {
				if (!loginStatus.isLoggedIn) {
					setPageTo("notLoggedInPage");
					return;
				}
				window.coursesLib.getProfile(function (response) {
					window.coursesLib.getUpcomingEvents(function (response) {
						for (var i = 0; i < response.events.length; i++) {
							var thisEvent = response.events[i];
							var $eventItem = $('

');

							$eventItem.children(".upcoming-title").text(thisEvent.title);
							$eventItem.children(".upcoming-course").text(thisEvent.course);
							var descThing = thisEvent.normText;
							if (descThing.length > 150) {
								descThing = descThing.substring(0, 150);
								descThing += "...";
							}
							$eventItem.children(".upcoming-desc").text(descThing + " ");

							var $readLink = $('see details →');
								$readLink.attr("href", thisEvent.link);
							$eventItem.children(".upcoming-desc").append($readLink);

							$("#upcomingEventList").append($eventItem);
							/*$(".upcoming-desc").each(function () {
								//$(this).text().sp
							});*/
						}
						setPageTo("mainPage");
					});
				});
			});
		} else {
			setPageTo("welcomePage");
		}
	});

	$("#soundsGood").click(function () {
		cpal.storage.setKey("showedMenubarWelcome", true, function () {
			window.location.reload();
		});
	});
	$("#noThanks").click(function () {
		chrome.tabs.create({
			url: cpal.resources.getURL("etc/disablemenu.html")
		});
	});
});<|endoftext|>


<commit_before>// Custom reveal.js integration
(function(){
	var isEnabled = true;

	document.querySelector( '.reveal .slides' ).addEventListener( 'mousedown', function( event ) {
		var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : 'alt' ) + 'Key';

		var zoomPadding = 20;
		var revealScale = Reveal.getScale();

		if( event[ modifier ] && isEnabled ) {
			event.preventDefault();

			var bounds = event.target.getBoundingClientRect();

			zoom.to({
				x: ( bounds.left * revealScale ) - zoomPadding,
				y: ( bounds.top * revealScale ) - zoomPadding,
				width: ( bounds.width * revealScale ) + ( zoomPadding * 2 ),
				height: ( bounds.height * revealScale ) + ( zoomPadding * 2 ),
				pan: false
			});
		}
	} );

	Reveal.addEventListener( 'overviewshown', function() { isEnabled = false; } );
	Reveal.addEventListener( 'overviewhidden', function() { isEnabled = true; } );
})();

/*!
 * zoom.js 0.3 (modified for use with reveal.js)
 * http://lab.hakim.se/zoom-js
 * MIT licensed
 *
 * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
 */
var zoom = (function(){

	// The current zoom level (scale)
	var level = 1;

	// The current mouse position, used for panning
	var mouseX = 0,
		mouseY = 0;

	// Timeout before pan is activated
	var panEngageTimeout = -1,
		panUpdateInterval = -1;

	// Check for transform support so that we can fallback otherwise
	var supportsTransforms = 	'WebkitTransform' in document.body.style ||
								'MozTransform' in document.body.style ||
								'msTransform' in document.body.style ||
								'OTransform' in document.body.style ||
								'transform' in document.body.style;

	if( supportsTransforms ) {
		// The easing that will be applied when we zoom in/out
		document.body.style.transition = 'transform 0.8s ease';
		document.body.style.OTransition = '-o-transform 0.8s ease';
		document.body.style.msTransition = '-ms-transform 0.8s ease';
		document.body.style.MozTransition = '-moz-transform 0.8s ease';
		document.body.style.WebkitTransition = '-webkit-transform 0.8s ease';
	}

	// Zoom out if the user hits escape
	document.addEventListener( 'keyup', function( event ) {
		if( level !== 1 && event.keyCode === 27 ) {
			zoom.out();
		}
	} );

	// Monitor mouse movement for panning
	document.addEventListener( 'mousemove', function( event ) {
		if( level !== 1 ) {
			mouseX = event.clientX;
			mouseY = event.clientY;
		}
	} );

	/**
	 * Applies the CSS required to zoom in, prefers the use of CSS3
	 * transforms but falls back on zoom for IE.
	 *
	 * @param {Object} rect
	 * @param {Number} scale
	 */
	function magnify( rect, scale ) {

		var scrollOffset = getScrollOffset();

		// Ensure a width/height is set
		rect.width = rect.width || 1;
		rect.height = rect.height || 1;

		// Center the rect within the zoomed viewport
		rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
		rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;

		if( supportsTransforms ) {
			// Reset
			if( scale === 1 ) {
				document.body.style.transform = '';
				document.body.style.OTransform = '';
				document.body.style.msTransform = '';
				document.body.style.MozTransform = '';
				document.body.style.WebkitTransform = '';
			}
			// Scale
			else {
				var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
					transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';

				document.body.style.transformOrigin = origin;
				document.body.style.OTransformOrigin = origin;
				document.body.style.msTransformOrigin = origin;
				document.body.style.MozTransformOrigin = origin;
				document.body.style.WebkitTransformOrigin = origin;

				document.body.style.transform = transform;
				document.body.style.OTransform = transform;
				document.body.style.msTransform = transform;
				document.body.style.MozTransform = transform;
				document.body.style.WebkitTransform = transform;
			}
		}
		else {
			// Reset
			if( scale === 1 ) {
				document.body.style.position = '';
				document.body.style.left = '';
				document.body.style.top = '';
				document.body.style.width = '';
				document.body.style.height = '';
				document.body.style.zoom = '';
			}
			// Scale
			else {
				document.body.style.position = 'relative';
				document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
				document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
				document.body.style.width = ( scale * 100 ) + '%';
				document.body.style.height = ( scale * 100 ) + '%';
				document.body.style.zoom = scale;
			}
		}

		level = scale;

		if( document.documentElement.classList ) {
			if( level !== 1 ) {
				document.documentElement.classList.add( 'zoomed' );
			}
			else {
				document.documentElement.classList.remove( 'zoomed' );
			}
		}
	}

	/**
	 * Pan the document when the mosue cursor approaches the edges
	 * of the window.
	 */
	function pan() {
		var range = 0.12,
			rangeX = window.innerWidth * range,
			rangeY = window.innerHeight * range,
			scrollOffset = getScrollOffset();

		// Up
		if( mouseY < rangeY ) {
			window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
		}
		// Down
		else if( mouseY > window.innerHeight - rangeY ) {
			window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
		}

		// Left
		if( mouseX < rangeX ) {
			window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
		}
		// Right
		else if( mouseX > window.innerWidth - rangeX ) {
			window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
		}
	}

	function getScrollOffset() {
		return {
			x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
			y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
		}
	}

	return {
		/**
		 * Zooms in on either a rectangle or HTML element.
		 *
		 * @param {Object} options
		 *   - element: HTML element to zoom in on
		 *   OR
		 *   - x/y: coordinates in non-transformed space to zoom in on
		 *   - width/height: the portion of the screen to zoom in on
		 *   - scale: can be used instead of width/height to explicitly set scale
		 */
		to: function( options ) {

			// Due to an implementation limitation we can't zoom in
			// to another element without zooming out first
			if( level !== 1 ) {
				zoom.out();
			}
			else {
				options.x = options.x || 0;
				options.y = options.y || 0;

				// If an element is set, that takes precedence
				if( !!options.element ) {
					// Space around the zoomed in element to leave on screen
					var padding = 20;
					var bounds = options.element.getBoundingClientRect();

					options.x = bounds.left - padding;
					options.y = bounds.top - padding;
					options.width = bounds.width + ( padding * 2 );
					options.height = bounds.height + ( padding * 2 );
				}

				// If width/height values are set, calculate scale from those values
				if( options.width !== undefined && options.height !== undefined ) {
					options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
				}

				if( options.scale > 1 ) {
					options.x *= options.scale;
					options.y *= options.scale;

					magnify( options, options.scale );

					if( options.pan !== false ) {

						// Wait with engaging panning as it may conflict with the
						// zoom transition
						panEngageTimeout = setTimeout( function() {
							panUpdateInterval = setInterval( pan, 1000 / 60 );
						}, 800 );

					}
				}
			}
		},

		/**
		 * Resets the document zoom state to its default.
		 */
		out: function() {
			clearTimeout( panEngageTimeout );
			clearInterval( panUpdateInterval );

			magnify( { x: 0, y: 0 }, 1 );

			level = 1;
		},

		// Alias
		magnify: function( options ) { this.to( options ) },
		reset: function() { this.out() },

		zoomLevel: function() {
			return level;
		}
	}

})();

<commit_msg>More natural zooming on block level elements<commit_after>// Custom reveal.js integration
(function(){
	var isEnabled = true;

	document.querySelector( '.reveal .slides' ).addEventListener( 'mousedown', function( event ) {
		var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : 'alt' ) + 'Key';

		var zoomPadding = 20;
		var revealScale = Reveal.getScale();

		if( event[ modifier ] && isEnabled ) {
			event.preventDefault();

			var bounds;
			var originalDisplay = event.target.style.display;

			// Get the bounding rect of the contents, not the containing box
			if (window.getComputedStyle(event.target).display === 'block') {
				event.target.style.display = 'inline-block';
				bounds = event.target.getBoundingClientRect();
				event.target.style.display = originalDisplay;
			} else {
				bounds = event.target.getBoundingClientRect();
			}

			zoom.to({
				x: ( bounds.left * revealScale ) - zoomPadding,
				y: ( bounds.top * revealScale ) - zoomPadding,
				width: ( bounds.width * revealScale ) + ( zoomPadding * 2 ),
				height: ( bounds.height * revealScale ) + ( zoomPadding * 2 ),
				pan: false
			});
		}
	} );

	Reveal.addEventListener( 'overviewshown', function() { isEnabled = false; } );
	Reveal.addEventListener( 'overviewhidden', function() { isEnabled = true; } );
})();

/*!
 * zoom.js 0.3 (modified for use with reveal.js)
 * http://lab.hakim.se/zoom-js
 * MIT licensed
 *
 * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
 */
var zoom = (function(){

	// The current zoom level (scale)
	var level = 1;

	// The current mouse position, used for panning
	var mouseX = 0,
		mouseY = 0;

	// Timeout before pan is activated
	var panEngageTimeout = -1,
		panUpdateInterval = -1;

	// Check for transform support so that we can fallback otherwise
	var supportsTransforms = 	'WebkitTransform' in document.body.style ||
								'MozTransform' in document.body.style ||
								'msTransform' in document.body.style ||
								'OTransform' in document.body.style ||
								'transform' in document.body.style;

	if( supportsTransforms ) {
		// The easing that will be applied when we zoom in/out
		document.body.style.transition = 'transform 0.8s ease';
		document.body.style.OTransition = '-o-transform 0.8s ease';
		document.body.style.msTransition = '-ms-transform 0.8s ease';
		document.body.style.MozTransition = '-moz-transform 0.8s ease';
		document.body.style.WebkitTransition = '-webkit-transform 0.8s ease';
	}

	// Zoom out if the user hits escape
	document.addEventListener( 'keyup', function( event ) {
		if( level !== 1 && event.keyCode === 27 ) {
			zoom.out();
		}
	} );

	// Monitor mouse movement for panning
	document.addEventListener( 'mousemove', function( event ) {
		if( level !== 1 ) {
			mouseX = event.clientX;
			mouseY = event.clientY;
		}
	} );

	/**
	 * Applies the CSS required to zoom in, prefers the use of CSS3
	 * transforms but falls back on zoom for IE.
	 *
	 * @param {Object} rect
	 * @param {Number} scale
	 */
	function magnify( rect, scale ) {

		var scrollOffset = getScrollOffset();

		// Ensure a width/height is set
		rect.width = rect.width || 1;
		rect.height = rect.height || 1;

		// Center the rect within the zoomed viewport
		rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
		rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;

		if( supportsTransforms ) {
			// Reset
			if( scale === 1 ) {
				document.body.style.transform = '';
				document.body.style.OTransform = '';
				document.body.style.msTransform = '';
				document.body.style.MozTransform = '';
				document.body.style.WebkitTransform = '';
			}
			// Scale
			else {
				var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
					transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';

				document.body.style.transformOrigin = origin;
				document.body.style.OTransformOrigin = origin;
				document.body.style.msTransformOrigin = origin;
				document.body.style.MozTransformOrigin = origin;
				document.body.style.WebkitTransformOrigin = origin;

				document.body.style.transform = transform;
				document.body.style.OTransform = transform;
				document.body.style.msTransform = transform;
				document.body.style.MozTransform = transform;
				document.body.style.WebkitTransform = transform;
			}
		}
		else {
			// Reset
			if( scale === 1 ) {
				document.body.style.position = '';
				document.body.style.left = '';
				document.body.style.top = '';
				document.body.style.width = '';
				document.body.style.height = '';
				document.body.style.zoom = '';
			}
			// Scale
			else {
				document.body.style.position = 'relative';
				document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
				document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
				document.body.style.width = ( scale * 100 ) + '%';
				document.body.style.height = ( scale * 100 ) + '%';
				document.body.style.zoom = scale;
			}
		}

		level = scale;

		if( document.documentElement.classList ) {
			if( level !== 1 ) {
				document.documentElement.classList.add( 'zoomed' );
			}
			else {
				document.documentElement.classList.remove( 'zoomed' );
			}
		}
	}

	/**
	 * Pan the document when the mosue cursor approaches the edges
	 * of the window.
	 */
	function pan() {
		var range = 0.12,
			rangeX = window.innerWidth * range,
			rangeY = window.innerHeight * range,
			scrollOffset = getScrollOffset();

		// Up
		if( mouseY < rangeY ) {
			window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
		}
		// Down
		else if( mouseY > window.innerHeight - rangeY ) {
			window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
		}

		// Left
		if( mouseX < rangeX ) {
			window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
		}
		// Right
		else if( mouseX > window.innerWidth - rangeX ) {
			window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
		}
	}

	function getScrollOffset() {
		return {
			x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
			y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
		}
	}

	return {
		/**
		 * Zooms in on either a rectangle or HTML element.
		 *
		 * @param {Object} options
		 *   - element: HTML element to zoom in on
		 *   OR
		 *   - x/y: coordinates in non-transformed space to zoom in on
		 *   - width/height: the portion of the screen to zoom in on
		 *   - scale: can be used instead of width/height to explicitly set scale
		 */
		to: function( options ) {

			// Due to an implementation limitation we can't zoom in
			// to another element without zooming out first
			if( level !== 1 ) {
				zoom.out();
			}
			else {
				options.x = options.x || 0;
				options.y = options.y || 0;

				// If an element is set, that takes precedence
				if( !!options.element ) {
					// Space around the zoomed in element to leave on screen
					var padding = 20;
					var bounds = options.element.getBoundingClientRect();

					options.x = bounds.left - padding;
					options.y = bounds.top - padding;
					options.width = bounds.width + ( padding * 2 );
					options.height = bounds.height + ( padding * 2 );
				}

				// If width/height values are set, calculate scale from those values
				if( options.width !== undefined && options.height !== undefined ) {
					options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
				}

				if( options.scale > 1 ) {
					options.x *= options.scale;
					options.y *= options.scale;

					magnify( options, options.scale );

					if( options.pan !== false ) {

						// Wait with engaging panning as it may conflict with the
						// zoom transition
						panEngageTimeout = setTimeout( function() {
							panUpdateInterval = setInterval( pan, 1000 / 60 );
						}, 800 );

					}
				}
			}
		},

		/**
		 * Resets the document zoom state to its default.
		 */
		out: function() {
			clearTimeout( panEngageTimeout );
			clearInterval( panUpdateInterval );

			magnify( { x: 0, y: 0 }, 1 );

			level = 1;
		},

		// Alias
		magnify: function( options ) { this.to( options ) },
		reset: function() { this.out() },

		zoomLevel: function() {
			return level;
		}
	}

})();

<|endoftext|>


<commit_before>const AkairoError = require('../../../util/AkairoError');
const Argument = require('./Argument');
const { ArgumentMatches } = require('../../../util/Constants');
const Flag = require('../Flag');

/**
 * Runs arguments.
 * @param {Command} command - Command to run for.
 * @private
 */
class ArgumentRunner {
    constructor(command) {
        this.command = command;
    }

    /**
     * The Akairo client.
     * @type {AkairoClient}
     */
    get client() {
        return this.command.client;
    }

    /**
     * The command handler.
     * @type {CommandHandler}
     */
    get handler() {
        return this.command.handler;
    }

    /**
     * Runs the arguments.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentGenerator} generator - Argument generator.
     * @returns {Promise<Flag|any>}
     */
    async run(message, parsed, generator) {
        const state = {
            usedIndices: new Set(),
            phraseIndex: 0,
            index: 0
        };

        const augmentRest = val => {
            if (Flag.is(val, 'continue')) {
                val.rest = parsed.all.slice(state.index).map(x => x.raw).join('');
            }
        };

        const iter = generator(message, parsed, state);
        let curr = await iter.next();
        while (!curr.done) {
            const value = curr.value;
            if (ArgumentRunner.isShortCircuit(value)) {
                augmentRest(value);
                return value;
            }

            const res = await this.runOne(message, parsed, state, new Argument(this.command, value));
            if (ArgumentRunner.isShortCircuit(res)) {
                augmentRest(res);
                return res;
            }

            curr = await iter.next(res);
        }

        augmentRest(curr.value);
        return curr.value;
    }

    /**
     * Runs one argument.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runOne(message, parsed, state, arg) {
        const cases = {
            [ArgumentMatches.PHRASE]: this.runPhrase,
            [ArgumentMatches.FLAG]: this.runFlag,
            [ArgumentMatches.OPTION]: this.runOption,
            [ArgumentMatches.REST]: this.runRest,
            [ArgumentMatches.SEPARATE]: this.runSeparate,
            [ArgumentMatches.TEXT]: this.runText,
            [ArgumentMatches.CONTENT]: this.runContent,
            [ArgumentMatches.REST_CONTENT]: this.runRestContent,
            [ArgumentMatches.NONE]: this.runNone
        };

        const runFn = cases[arg.match];
        if (runFn == null) {
            throw new AkairoError('UNKNOWN_MATCH_TYPE', arg.match);
        }

        return runFn.call(this, message, parsed, state, arg);
    }

    /**
     * Runs `phrase` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runPhrase(message, parsed, state, arg) {
        if (arg.unordered || arg.unordered === 0) {
            const indices = typeof unordered === 'number'
                ? Array.from(parsed.phrases.keys()).slice(arg.unordered)
                : Array.isArray(arg.unordered)
                    ? arg.unordered
                    : Array.from(parsed.phrases.keys());

            for (const i of indices) {
                if (state.usedIndices.has(i)) {
                    continue;
                }

                const phrase = parsed.phrases[i] ? parsed.phrases[i].value : '';
                // `cast` is used instead of `process` since we do not want prompts.
                const res = await arg.cast(message, phrase);
                if (res != null) {
                    state.usedIndices.add(i);
                    return res;
                }
            }

            // No indices matched.
            return arg.process(message, '');
        }

        const index = arg.index == null ? state.phraseIndex : arg.index;
        const ret = arg.process(message, parsed.phrases[index] ? parsed.phrases[index].value : '');
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `rest` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runRest(message, parsed, state, arg) {
        const index = arg.index == null ? state.phraseIndex : arg.index;
        const rest = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        const ret = await arg.process(message, rest);
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `separate` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runSeparate(message, parsed, state, arg) {
        const index = arg.index == null ? state.phraseIndex : arg.index;
        const phrases = parsed.phrases.slice(index, index + arg.limit);
        if (!phrases.length) {
            const ret = await arg.process(message, '');
            if (arg.index != null) {
                ArgumentRunner.increaseIndex(parsed, state);
            }

            return ret;
        }

        const res = [];
        for (const phrase of phrases) {
            res.push(await arg.process(message, phrase.value));
        }

        if (arg.index != null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return res;
    }

    /**
     * Runs `flag` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runFlag(message, parsed, state, arg) {
        const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
        if (arg.multipleFlags) {
            const amount = parsed.flags.filter(flag =>
                names.some(name =>
                    name.toLowerCase() === flag.key.toLowerCase()
                )
            ).length;

            return amount;
        }

        const flagFound = parsed.flags.some(flag =>
            names.some(name =>
                name.toLowerCase() === flag.key.toLowerCase()
            )
        );

        return arg.default == null ? flagFound : !flagFound;
    }

    /**
     * Runs `option` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runOption(message, parsed, state, arg) {
        const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
        if (arg.multipleFlags) {
            const values = parsed.optionFlags.filter(flag =>
                names.some(name =>
                    name.toLowerCase() === flag.key.toLowerCase()
                )
            ).map(x => x.value).slice(0, arg.limit);

            const res = [];
            for (const value of values) {
                res.push(await arg.process(message, value));
            }

            return res;
        }

        const foundFlag = parsed.optionFlags.find(flag =>
            names.some(name =>
                name.toLowerCase() === flag.key.toLowerCase()
            )
        );

        return arg.process(message, foundFlag != null ? foundFlag.value : '');
    }

    /**
     * Runs `text` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runText(message, parsed, state, arg) {
        const index = arg.index == null ? 0 : arg.index;
        const text = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        return arg.process(message, text);
    }

    /**
     * Runs `content` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runContent(message, parsed, state, arg) {
        const index = arg.index == null ? 0 : arg.index;
        const content = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        return arg.process(message, content);
    }

    /**
     * Runs `restContent` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runRestContent(message, parsed, state, arg) {
        const index = arg.index == null ? state.index : arg.index;
        const rest = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        const ret = await arg.process(message, rest);
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `none` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runNone(message, parsed, state, arg) {
        return arg.process(message, '');
    }

    /**
     * Modifies state by incrementing the indices.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {number} n - Number of indices to increase by.
     * @returns {Promise<Flag|any>}
     */
    static increaseIndex(parsed, state, n = 1) {
        state.phraseIndex += n;
        while (n > 0) {
            do {
                state.index++;
            } while (parsed.all[state.index] && parsed.all[state.index].type !== 'Phrase');
            n--;
        }
    }

    /**
     * Checks if something is a flag that short circuits.
     * @param {any} value - A value.
     * @returns {boolean}
     */
    static isShortCircuit(value) {
        return Flag.is(value, 'cancel') || Flag.is(value, 'retry') || Flag.is(value, 'continue');
    }

    /**
     * Creates an argument generator from argument options.
     * @param {ArgumentOptions[]} args - Argument options.
     * @returns {GeneratorFunction}
     */
    static fromArguments(args) {
        return function* generate() {
            const res = {};
            for (const [id, arg] of args) {
                res[id] = yield arg;
            }

            return res;
        };
    }
}

module.exports = ArgumentRunner;

/**
 * State for the argument runner.
 * @typedef {Object} ArgumentRunnerState
 * @prop {Set<number>} usedIndices - Indices already used for unordered match.
 * @prop {number} phraseIndex - Index in terms of phrases.
 * @prop {number} index - Index in terms of the raw strings.
 */
<commit_msg>fix: return a cancel flag if the user cancels a prompt in separate match (#139)<commit_after>const AkairoError = require('../../../util/AkairoError');
const Argument = require('./Argument');
const { ArgumentMatches } = require('../../../util/Constants');
const Flag = require('../Flag');

/**
 * Runs arguments.
 * @param {Command} command - Command to run for.
 * @private
 */
class ArgumentRunner {
    constructor(command) {
        this.command = command;
    }

    /**
     * The Akairo client.
     * @type {AkairoClient}
     */
    get client() {
        return this.command.client;
    }

    /**
     * The command handler.
     * @type {CommandHandler}
     */
    get handler() {
        return this.command.handler;
    }

    /**
     * Runs the arguments.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentGenerator} generator - Argument generator.
     * @returns {Promise<Flag|any>}
     */
    async run(message, parsed, generator) {
        const state = {
            usedIndices: new Set(),
            phraseIndex: 0,
            index: 0
        };

        const augmentRest = val => {
            if (Flag.is(val, 'continue')) {
                val.rest = parsed.all.slice(state.index).map(x => x.raw).join('');
            }
        };

        const iter = generator(message, parsed, state);
        let curr = await iter.next();
        while (!curr.done) {
            const value = curr.value;
            if (ArgumentRunner.isShortCircuit(value)) {
                augmentRest(value);
                return value;
            }

            const res = await this.runOne(message, parsed, state, new Argument(this.command, value));
            if (ArgumentRunner.isShortCircuit(res)) {
                augmentRest(res);
                return res;
            }

            curr = await iter.next(res);
        }

        augmentRest(curr.value);
        return curr.value;
    }

    /**
     * Runs one argument.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runOne(message, parsed, state, arg) {
        const cases = {
            [ArgumentMatches.PHRASE]: this.runPhrase,
            [ArgumentMatches.FLAG]: this.runFlag,
            [ArgumentMatches.OPTION]: this.runOption,
            [ArgumentMatches.REST]: this.runRest,
            [ArgumentMatches.SEPARATE]: this.runSeparate,
            [ArgumentMatches.TEXT]: this.runText,
            [ArgumentMatches.CONTENT]: this.runContent,
            [ArgumentMatches.REST_CONTENT]: this.runRestContent,
            [ArgumentMatches.NONE]: this.runNone
        };

        const runFn = cases[arg.match];
        if (runFn == null) {
            throw new AkairoError('UNKNOWN_MATCH_TYPE', arg.match);
        }

        return runFn.call(this, message, parsed, state, arg);
    }

    /**
     * Runs `phrase` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runPhrase(message, parsed, state, arg) {
        if (arg.unordered || arg.unordered === 0) {
            const indices = typeof unordered === 'number'
                ? Array.from(parsed.phrases.keys()).slice(arg.unordered)
                : Array.isArray(arg.unordered)
                    ? arg.unordered
                    : Array.from(parsed.phrases.keys());

            for (const i of indices) {
                if (state.usedIndices.has(i)) {
                    continue;
                }

                const phrase = parsed.phrases[i] ? parsed.phrases[i].value : '';
                // `cast` is used instead of `process` since we do not want prompts.
                const res = await arg.cast(message, phrase);
                if (res != null) {
                    state.usedIndices.add(i);
                    return res;
                }
            }

            // No indices matched.
            return arg.process(message, '');
        }

        const index = arg.index == null ? state.phraseIndex : arg.index;
        const ret = arg.process(message, parsed.phrases[index] ? parsed.phrases[index].value : '');
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `rest` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runRest(message, parsed, state, arg) {
        const index = arg.index == null ? state.phraseIndex : arg.index;
        const rest = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        const ret = await arg.process(message, rest);
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `separate` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runSeparate(message, parsed, state, arg) {
        const index = arg.index == null ? state.phraseIndex : arg.index;
        const phrases = parsed.phrases.slice(index, index + arg.limit);
        if (!phrases.length) {
            const ret = await arg.process(message, '');
            if (arg.index != null) {
                ArgumentRunner.increaseIndex(parsed, state);
            }

            return ret;
        }

        const res = [];
        for (const phrase of phrases) {
            const response = await arg.process(message, phrase.value);

            if (Flag.is(response, 'cancel')) {
                return response;
            }

            res.push(response);
        }

        if (arg.index != null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return res;
    }

    /**
     * Runs `flag` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runFlag(message, parsed, state, arg) {
        const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
        if (arg.multipleFlags) {
            const amount = parsed.flags.filter(flag =>
                names.some(name =>
                    name.toLowerCase() === flag.key.toLowerCase()
                )
            ).length;

            return amount;
        }

        const flagFound = parsed.flags.some(flag =>
            names.some(name =>
                name.toLowerCase() === flag.key.toLowerCase()
            )
        );

        return arg.default == null ? flagFound : !flagFound;
    }

    /**
     * Runs `option` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runOption(message, parsed, state, arg) {
        const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
        if (arg.multipleFlags) {
            const values = parsed.optionFlags.filter(flag =>
                names.some(name =>
                    name.toLowerCase() === flag.key.toLowerCase()
                )
            ).map(x => x.value).slice(0, arg.limit);

            const res = [];
            for (const value of values) {
                res.push(await arg.process(message, value));
            }

            return res;
        }

        const foundFlag = parsed.optionFlags.find(flag =>
            names.some(name =>
                name.toLowerCase() === flag.key.toLowerCase()
            )
        );

        return arg.process(message, foundFlag != null ? foundFlag.value : '');
    }

    /**
     * Runs `text` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runText(message, parsed, state, arg) {
        const index = arg.index == null ? 0 : arg.index;
        const text = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        return arg.process(message, text);
    }

    /**
     * Runs `content` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runContent(message, parsed, state, arg) {
        const index = arg.index == null ? 0 : arg.index;
        const content = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        return arg.process(message, content);
    }

    /**
     * Runs `restContent` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    async runRestContent(message, parsed, state, arg) {
        const index = arg.index == null ? state.index : arg.index;
        const rest = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
        const ret = await arg.process(message, rest);
        if (arg.index == null) {
            ArgumentRunner.increaseIndex(parsed, state);
        }

        return ret;
    }

    /**
     * Runs `none` match.
     * @param {Message} message - Message that triggered the command.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {Argument} arg - Current argument.
     * @returns {Promise<Flag|any>}
     */
    runNone(message, parsed, state, arg) {
        return arg.process(message, '');
    }

    /**
     * Modifies state by incrementing the indices.
     * @param {ContentParserResult} parsed - Parsed data from ContentParser.
     * @param {ArgumentRunnerState} state - Argument handling state.
     * @param {number} n - Number of indices to increase by.
     * @returns {Promise<Flag|any>}
     */
    static increaseIndex(parsed, state, n = 1) {
        state.phraseIndex += n;
        while (n > 0) {
            do {
                state.index++;
            } while (parsed.all[state.index] && parsed.all[state.index].type !== 'Phrase');
            n--;
        }
    }

    /**
     * Checks if something is a flag that short circuits.
     * @param {any} value - A value.
     * @returns {boolean}
     */
    static isShortCircuit(value) {
        return Flag.is(value, 'cancel') || Flag.is(value, 'retry') || Flag.is(value, 'continue');
    }

    /**
     * Creates an argument generator from argument options.
     * @param {ArgumentOptions[]} args - Argument options.
     * @returns {GeneratorFunction}
     */
    static fromArguments(args) {
        return function* generate() {
            const res = {};
            for (const [id, arg] of args) {
                res[id] = yield arg;
            }

            return res;
        };
    }
}

module.exports = ArgumentRunner;

/**
 * State for the argument runner.
 * @typedef {Object} ArgumentRunnerState
 * @prop {Set<number>} usedIndices - Indices already used for unordered match.
 * @prop {number} phraseIndex - Index in terms of phrases.
 * @prop {number} index - Index in terms of the raw strings.
 */
<|endoftext|>