<commit_before>const Command = require("../base/Command");

class Poll extends Command {
    constructor(client) {
        super(client, {
            name: "poll",
            category: "Misc",
            aliases: ["vote"],
            flags: {
                anon: {
                    aliases: ["anonymous"]
                }
            },
            subArgs: {
                pollID: {
                    aliases: ["poll"],
                    default: null
                }
            }
        });
    }

    async run(client, message, [action, ...opts], options) {
        const level = options.level;
        if (!action) {
            return message.channel.send(message.language.get("COMMAND_POLL_NO_ARG"));
        }
        let poll = { // ID = guildID-channelID
            "question": "",
            "options": [],
            "votes": {
                // userID: vote#
            },
            "anon": false
        };
        const optsJoin = opts.join(" ").split("|");
        let pollID;
        let exists = false;
        // If subArgs.poll is there, get that instead, and check if it's for a channel
        // that the user has access to
        if (options.subArgs.pollID) {
            // If they're using the auto-incrementing ID to vote from anywhere
            exists = await client.database.models.polls.findOne({where: {pollId: options.subArgs.pollID}})
                .then(token => token != null)
                .then(isUnique => isUnique);

            if (exists) {
                const tempP = await client.database.models.polls.findOne({where: {pollId: options.subArgs.pollID}, attributes: ["id", "poll", "pollId"]});
                const thisPoll = tempP.dataValues;
                poll = thisPoll.poll;
                poll.pollID = thisPoll.pollId;
                pollID = thisPoll.id;
                const [guildID, chanID] = pollID.split("-");

                // If they do not have access to the channel, tell em so/ don't let them interact with the poll (see/ vote)
                if (client.guilds.has(guildID) && client.guilds.get(guildID).channels.has(chanID)) {
                    const perms = client.guilds.get(guildID).channels.get(chanID).permissionsFor(message.author.id);
                    if (!perms || !perms.has("READ_MESSAGES")) {
                        // The guild and channel exist, but the message author cannot see it
                        return message.channel.send(message.language.get("COMMAND_POLL_NO_ACCESS"));
                    }
                }
                if (message.guild && message.guild.id !== guildID) {
                    // Make it so remote voting is only usable within DMs or in the same guild
                    return message.channel.send("Sorry, but you must be in a DM or in the same server to vote remotely.");
                }
            } else {
                return message.channel.send(message.language.get("COMMAND_POLL_INVALID_ID"));
            }
            const actions = ["view", "check"];
            if (!actions.includes(action.toLowerCase()) && !action.match(/\d/)) {
                return message.channel.send(message.language.get("COMMAND_POLL_REMOTE_OPTS"));
            }
            // If they get past all the stuff before here, they should be good to go
        } else if (!message.guild) {
            // If they're trying to use this in a DM
            return message.channel.send(message.language.get("COMMAND_POLL_DM_USE", message.guildSettings.prefix));
        } else {
            // If they're just voting on the channel's poll
            pollID = `${message.guild.id}-${message.channel.id}`;
            exists = await client.database.models.polls.findOne({where: {id: pollID}})
                .then(token => token != null)
                .then(isUnique => isUnique);

            if (exists) {
                const tempP = await client.database.models.polls.findOne({where: {id: pollID}});
                poll = tempP.dataValues.poll;
                poll.pollID = tempP.dataValues.pollId;
            }
        }
        switch (action) {
            case "start":
            case "create": {
                // Create a poll (lvl 3+)
                if (level < 3) {
                    return message.channel.send(message.language.get("COMMAND_MISSING_PERMS"));
                }
                if (exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_ALREADY_RUNNING"));
                }
                if (!optsJoin[0]) {
                    return message.channel.send(message.language.get("COMMAND_POLL_MISSING_QUESTION"));
                } else {
                    poll.question = optsJoin[0];
                    optsJoin.splice(0,1);
                    if (poll.question.length >= 256) {
                        return message.channel.send(message.language.get("COMMAND_POLL_TITLE_TOO_LONG"));
                    }
                }
                if (optsJoin.length < 2) {
                    return message.channel.send(message.language.get("COMMAND_POLL_TOO_FEW_OPT"));
                } else if (optsJoin.length > 10) {
                    return message.channel.send(message.language.get("COMMAND_POLL_TOO_MANY_OPT"));
                } else {
                    poll.options = optsJoin.map(opt => opt.trim());
                }
                if (options.flags.anon) {
                    poll.anon = true;
                }
                await client.database.models.polls.create({
                    id: pollID,
                    poll: poll
                })
                    .then((thisPoll) => {
                        poll.pollID = thisPoll.dataValues.pollId;
                        return message.channel.send(message.language.get("COMMAND_POLL_CREATED", message.author.tag, message.guildSettings.prefix), {embed: {
                            author: {
                                name: poll.question
                            },
                            description: pollCheck(poll),
                            footer: getFooter(poll)
                        }});
                    });
                break;
            }
            case "view":
            case "check": {
                // Check the current poll
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    if (poll.question.length > 256) {
                        // Should not happen, but just in case
                        return message.channel.send({embed: {
                            author: {
                                name: "Current Poll"
                            },
                            description: `**${poll.question}**\n\n${pollCheck(poll)}`,
                            footer: getFooter(poll)
                        }});
                    } else {
                        return message.channel.send({embed: {
                            author: {
                                name: poll.question
                            },
                            description: pollCheck(poll),
                            footer: getFooter(poll)
                        }});
                    }
                }
            }
            case "close":
            case "end": {
                // Close the current poll and send the results (lvl 3+)
                if (level < 3) {
                    return message.channel.send(message.language.get("COMMAND_MISSING_PERMS"));
                }
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    // Delete the current poll
                    await client.database.models.polls.destroy({where: {id: pollID}})
                        .then(() => {
                            // Then send a message showing the final results
                            return message.channel.send({embed: {
                                author: {
                                    name: message.language.get("COMMAND_POLL_FINAL", "")
                                },
                                description: `**${poll.question}**\n${pollCheck(poll, true)}`
                            }});
                        })
                        .catch(() => {
                            // Or, if it breaks, tell them that it broke
                            return message.channel.send(message.language.get("COMMAND_POLL_FINAL_ERROR", poll.question));
                        });
                }
                break;
            }
            case String(action.match(/\d/)): {
                // Someone voting on an option
                // Check if there is a poll going, then if there is, vote, else tell em that there isn"t
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    const opt = Math.abs(parseInt(action)-1);
                    if (poll.options.length < opt) {
                        return message.channel.send(message.language.get("COMMAND_POLL_INVALID_OPTION"));
                    } else {
                        let voted = -1;
                        if (poll.votes[message.author.id] === opt) {
                            return message.channel.send(message.language.get("COMMAND_POLL_SAME_OPT", poll.options[opt]));
                        } else if (poll.votes.hasOwnProperty(message.author.id)) {
                            voted = poll.votes[message.author.id];
                        }
                        poll.votes[message.author.id] = opt;
                        await client.database.models.polls.update({poll: poll}, {where: {id: pollID}})
                            .then(() => {
                                if (voted !== -1) {
                                    return message.channel.send(message.language.get("COMMAND_POLL_CHANGED_OPT", poll.options[voted], poll.options[opt]));
                                } else {
                                    return message.channel.send(message.language.get("COMMAND_POLL_REGISTERED", poll.options[opt]));
                                }
                            });
                    }
                }
                break;
            }
            case "me": {
                if (poll.question && poll.question.length) {
                    try {
                        await message.author.send(message.language.get("COMMAND_POLL_ME1", poll.pollID, pollCheck(poll)));
                        return message.author.send(message.language.get("COMMAND_POLL_ME2", message.guildSettings.prefix, poll.pollID));
                    } catch (e) {
                        return message.channel.send(message.language.get("BASE_CANNOT_DM"));
                    }
                } else {
                    // no poll active
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                }
            }
            default: {
                // Help
                return;
            }
        }

        function pollCheck(poll, showRes=false) {
            const voteCount = {};
            const totalVotes = Object.keys(poll.votes).length || 1;
            poll.options.forEach((opt, ix) => {
                voteCount[ix] = 0;
            });
            Object.keys(poll.votes).forEach(voter => {
                voteCount[poll.votes[voter]] += 1;
            });
            let outString = "";
            Object.keys(voteCount).forEach(opt => {
                const percent = Math.floor((voteCount[opt] / totalVotes) * 30);
                outString += `\`[${parseInt(opt)+1}]\` **${poll.options[opt]}**\n`;
                if (!poll.anon || showRes) {
                    // If the poll is not set to anonymous, or it's told to show the results (for closing the poll)
                    outString += `**\`[${"#".repeat(percent)}${"-".repeat(30-percent)}]\`**(${voteCount[opt]})\n`;
                }
            });
            return outString;
        }

        function getFooter(poll) {
            const footer = {};
            if (message.guild) {
                footer.text = client.expandSpaces(message.language.get("COMMAND_POLL_FOOTER", poll.pollID, message.guildSettings.prefix));
            } else {
                footer.text = client.expandSpaces(message.language.get("COMMAND_POLL_DM_FOOTER", poll.pollID, message.guildSettings.prefix));
            }
            return footer;
        }
    }
}

module.exports = Poll;

<commit_msg>Fix for bad option checking<commit_after>const Command = require("../base/Command");

class Poll extends Command {
    constructor(client) {
        super(client, {
            name: "poll",
            category: "Misc",
            aliases: ["vote"],
            flags: {
                anon: {
                    aliases: ["anonymous"]
                }
            },
            subArgs: {
                pollID: {
                    aliases: ["poll"],
                    default: null
                }
            }
        });
    }

    async run(client, message, [action, ...opts], options) {
        const level = options.level;
        if (!action) {
            return message.channel.send(message.language.get("COMMAND_POLL_NO_ARG"));
        }
        let poll = { // ID = guildID-channelID
            "question": "",
            "options": [],
            "votes": {
                // userID: vote#
            },
            "anon": false
        };
        const optsJoin = opts.join(" ").split("|");
        let pollID;
        let exists = false;
        // If subArgs.poll is there, get that instead, and check if it's for a channel
        // that the user has access to
        if (options.subArgs.pollID) {
            // If they're using the auto-incrementing ID to vote from anywhere
            exists = await client.database.models.polls.findOne({where: {pollId: options.subArgs.pollID}})
                .then(token => token != null)
                .then(isUnique => isUnique);

            if (exists) {
                const tempP = await client.database.models.polls.findOne({where: {pollId: options.subArgs.pollID}, attributes: ["id", "poll", "pollId"]});
                const thisPoll = tempP.dataValues;
                poll = thisPoll.poll;
                poll.pollID = thisPoll.pollId;
                pollID = thisPoll.id;
                const [guildID, chanID] = pollID.split("-");

                // If they do not have access to the channel, tell em so/ don't let them interact with the poll (see/ vote)
                if (client.guilds.has(guildID) && client.guilds.get(guildID).channels.has(chanID)) {
                    const perms = client.guilds.get(guildID).channels.get(chanID).permissionsFor(message.author.id);
                    if (!perms || !perms.has("READ_MESSAGES")) {
                        // The guild and channel exist, but the message author cannot see it
                        return message.channel.send(message.language.get("COMMAND_POLL_NO_ACCESS"));
                    }
                }
                if (message.guild && message.guild.id !== guildID) {
                    // Make it so remote voting is only usable within DMs or in the same guild
                    return message.channel.send("Sorry, but you must be in a DM or in the same server to vote remotely.");
                }
            } else {
                return message.channel.send(message.language.get("COMMAND_POLL_INVALID_ID"));
            }
            const actions = ["view", "check"];
            if (!actions.includes(action.toLowerCase()) && !action.match(/\d/)) {
                return message.channel.send(message.language.get("COMMAND_POLL_REMOTE_OPTS"));
            }
            // If they get past all the stuff before here, they should be good to go
        } else if (!message.guild) {
            // If they're trying to use this in a DM
            return message.channel.send(message.language.get("COMMAND_POLL_DM_USE", message.guildSettings.prefix));
        } else {
            // If they're just voting on the channel's poll
            pollID = `${message.guild.id}-${message.channel.id}`;
            exists = await client.database.models.polls.findOne({where: {id: pollID}})
                .then(token => token != null)
                .then(isUnique => isUnique);

            if (exists) {
                const tempP = await client.database.models.polls.findOne({where: {id: pollID}});
                poll = tempP.dataValues.poll;
                poll.pollID = tempP.dataValues.pollId;
            }
        }
        switch (action) {
            case "start":
            case "create": {
                // Create a poll (lvl 3+)
                if (level < 3) {
                    return message.channel.send(message.language.get("COMMAND_MISSING_PERMS"));
                }
                if (exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_ALREADY_RUNNING"));
                }
                if (!optsJoin[0]) {
                    return message.channel.send(message.language.get("COMMAND_POLL_MISSING_QUESTION"));
                } else {
                    poll.question = optsJoin[0];
                    optsJoin.splice(0,1);
                    if (poll.question.length >= 256) {
                        return message.channel.send(message.language.get("COMMAND_POLL_TITLE_TOO_LONG"));
                    }
                }
                if (optsJoin.length < 2) {
                    return message.channel.send(message.language.get("COMMAND_POLL_TOO_FEW_OPT"));
                } else if (optsJoin.length > 10) {
                    return message.channel.send(message.language.get("COMMAND_POLL_TOO_MANY_OPT"));
                } else {
                    poll.options = optsJoin.map(opt => opt.trim());
                }
                if (options.flags.anon) {
                    poll.anon = true;
                }
                await client.database.models.polls.create({
                    id: pollID,
                    poll: poll
                })
                    .then((thisPoll) => {
                        poll.pollID = thisPoll.dataValues.pollId;
                        return message.channel.send(message.language.get("COMMAND_POLL_CREATED", message.author.tag, message.guildSettings.prefix), {embed: {
                            author: {
                                name: poll.question
                            },
                            description: pollCheck(poll),
                            footer: getFooter(poll)
                        }});
                    });
                break;
            }
            case "view":
            case "check": {
                // Check the current poll
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    if (poll.question.length > 256) {
                        // Should not happen, but just in case
                        return message.channel.send({embed: {
                            author: {
                                name: "Current Poll"
                            },
                            description: `**${poll.question}**\n\n${pollCheck(poll)}`,
                            footer: getFooter(poll)
                        }});
                    } else {
                        return message.channel.send({embed: {
                            author: {
                                name: poll.question
                            },
                            description: pollCheck(poll),
                            footer: getFooter(poll)
                        }});
                    }
                }
            }
            case "close":
            case "end": {
                // Close the current poll and send the results (lvl 3+)
                if (level < 3) {
                    return message.channel.send(message.language.get("COMMAND_MISSING_PERMS"));
                }
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    // Delete the current poll
                    await client.database.models.polls.destroy({where: {id: pollID}})
                        .then(() => {
                            // Then send a message showing the final results
                            return message.channel.send({embed: {
                                author: {
                                    name: message.language.get("COMMAND_POLL_FINAL", "")
                                },
                                description: `**${poll.question}**\n${pollCheck(poll, true)}`
                            }});
                        })
                        .catch(() => {
                            // Or, if it breaks, tell them that it broke
                            return message.channel.send(message.language.get("COMMAND_POLL_FINAL_ERROR", poll.question));
                        });
                }
                break;
            }
            case String(action.match(/\d/)): {
                // Someone voting on an option
                // Check if there is a poll going, then if there is, vote, else tell em that there isn"t
                if (!exists) {
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                } else {
                    const opt = Math.abs(parseInt(action)) - 1;
                    if (poll.options.length <= opt || opt < 0) {
                        return message.channel.send(message.language.get("COMMAND_POLL_INVALID_OPTION"));
                    } else {
                        let voted = -1;
                        if (poll.votes[message.author.id] === opt) {
                            return message.channel.send(message.language.get("COMMAND_POLL_SAME_OPT", poll.options[opt]));
                        } else if (poll.votes.hasOwnProperty(message.author.id)) {
                            voted = poll.votes[message.author.id];
                        }
                        poll.votes[message.author.id] = opt;
                        await client.database.models.polls.update({poll: poll}, {where: {id: pollID}})
                            .then(() => {
                                if (voted !== -1) {
                                    return message.channel.send(message.language.get("COMMAND_POLL_CHANGED_OPT", poll.options[voted], poll.options[opt]));
                                } else {
                                    return message.channel.send(message.language.get("COMMAND_POLL_REGISTERED", poll.options[opt]));
                                }
                            });
                    }
                }
                break;
            }
            case "me": {
                if (poll.question && poll.question.length) {
                    try {
                        await message.author.send(message.language.get("COMMAND_POLL_ME1", poll.pollID, pollCheck(poll)));
                        return message.author.send(message.language.get("COMMAND_POLL_ME2", message.guildSettings.prefix, poll.pollID));
                    } catch (e) {
                        return message.channel.send(message.language.get("BASE_CANNOT_DM"));
                    }
                } else {
                    // no poll active
                    return message.channel.send(message.language.get("COMMAND_POLL_NO_POLL"));
                }
            }
            default: {
                // Help
                return;
            }
        }

        function pollCheck(poll, showRes=false) {
            const voteCount = {};
            const totalVotes = Object.keys(poll.votes).length || 1;
            poll.options.forEach((opt, ix) => {
                voteCount[ix] = 0;
            });
            Object.keys(poll.votes).forEach(voter => {
                voteCount[poll.votes[voter]] += 1;
            });
            let outString = "";
            Object.keys(voteCount).forEach(opt => {
                const percent = Math.floor((voteCount[opt] / totalVotes) * 30);
                outString += `\`[${parseInt(opt)+1}]\` **${poll.options[opt]}**\n`;
                if (!poll.anon || showRes) {
                    // If the poll is not set to anonymous, or it's told to show the results (for closing the poll)
                    outString += `**\`[${"#".repeat(percent)}${"-".repeat(30-percent)}]\`**(${voteCount[opt]})\n`;
                }
            });
            return outString;
        }

        function getFooter(poll) {
            const footer = {};
            if (message.guild) {
                footer.text = client.expandSpaces(message.language.get("COMMAND_POLL_FOOTER", poll.pollID, message.guildSettings.prefix));
            } else {
                footer.text = client.expandSpaces(message.language.get("COMMAND_POLL_DM_FOOTER", poll.pollID, message.guildSettings.prefix));
            }
            return footer;
        }
    }
}

module.exports = Poll;

<|endoftext|>


<commit_before>module.exports = {
  description: '',
  afterInstall: function () {
    var self = this
    return self.addAddonsToProject({
      packages: [
        {name: 'ember-frost-core', target: '^1.0.0'},
        {name: 'ember-prop-types', target: '^3.0.0'},
        {name: 'ember-hook', target: '^1.3.5'}
      ]
    }).then(function () {
      return self.addBowerPackagesToProject([
        {name: 'moment'},
        {name: 'pikaday'}
      ])
    })
  },
  normalizeEntityName: function () {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  }
}
<commit_msg>up blueprint<commit_after>module.exports = {
  description: '',
  afterInstall: function () {
    var self = this
    return self.addAddonsToProject({
      packages: [
        {name: 'ember-frost-core', target: '^1.0.0'},
        {name: 'ember-prop-types', target: '^3.0.0'},
        {name: 'ember-hook', target: '^1.3.5'}
      ]
    }).then(function () {
      return self.addBowerPackagesToProject([
        {name: 'moment'},
        {name: 'pikaday', target: '~1.5.1'},
        {name: 'clockpicker-seconds', target: '~0.1.6'}
      ])
    })
  },
  normalizeEntityName: function () {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  }
}
<|endoftext|>


<commit_before>
/*!  jquery-holdup - */

/* global define:false */

/*  *** USAGE ***
    JavaScript:
        // these are the open or commonly used api methods
        $(selector).holdup({override_options})     // create instance
        $(selector).holdup('render')               // will determine if element should be shown
        $(selector).holdup('show')                 // will force elements to be loaded

        // these are exposed really for debugging purposes...
        $(selector).holdup('observe')              // will add container ( window ) event listeners if listeners have not been added
        $(selector).holdup('ignore')               // will remove container ( window ) event listeners

    HTML:
        // NOTE: based on DEFAULT options
        // src-retina path is for displays that support hi-dpi images

    CSS:
        // NOTE: based on DEFAULT options
        .heldup {
            rules ... rules
        }
        .heldup.heldup-success {
            rules ... rules
        }
        .heldup.heldup-error{
            rules ... rules
        }
*/
;( function ( factory )
{
    'use strict';

    if ( typeof define === 'function' && define.amd )
    {
        // AMD. Register as an anonymous module.
        define( ['jquery'], factory );
    }
    else
    {
        // Browser globals
        factory( jQuery );
    }
}
( function ( $ )
{
    'use strict';

    /* HELPER FUNCTIONS */
    // PURPOSE: check if element is in view to display
    // @param $container : $(DOMElement) of the scrollable view
    // @param $el : $(DOMElement)
    // @param threshold : Number
    // return bool
    var is_element_in_view = function( $container, $el, threshold )
    {
        // PURPOSE: check if element is in vertical viewport
        // @param el_top : Number
        // return bool
        var is_element_in_vertical_view = function( top )
        {
            var container_viewport_top = $container.scrollTop();
            return top + $el.height() >= container_viewport_top - threshold &&     // element bottom is within the viewport top
                top <= container_viewport_top + $container.height() + threshold    // element top is within the viewport bottom
                ;
        };

        // PURPOSE: check if element is in horizontal viewport
        // @param el_left : Number
        // return bool
        var is_element_in_horizontal_view = function( left )
        {
            var container_viewport_left = $container.scrollLeft();
            return left <= container_viewport_left + $container.width() &&         // element left is within the viewport right
                left + $el.width() >= container_viewport_left                      // element right is within the viewport left
                ;
        };

        // cache the element's coordinates on the page...
        var element_offset = $el.offset();

        return  is_element_in_vertical_view( element_offset.top ) &&
                is_element_in_horizontal_view( element_offset.left )
                ;
    };

    // PURPOSE: see if there are any more images to load -- see if rendering needs to be run
    // @param evt : $(Event)
    var do_render_view = function( evt )
    {
        // don't fire unless its by a human
        if ( evt.isTrigger )
        {
            return;
        }
        // selects the imgs waiting to be loaded
        var $imgs_not_loaded = $( '.' + pending_classname );

        // if there are images needing to load
        if ( $imgs_not_loaded.length )
        {
            // do the render method
            $imgs_not_loaded.holdup('render');
        }
        // or if the plugin observe has been initialized
        else if ( is_initialized )
        {
            // remove the events from the window
            HoldupProto.ignore();
        }
        // or nothing at all
    };

    // inspired by underscore's debounce and throttle routines
    var do_bottlenecked_event = function( fn, delay, is_resize )
    {
        var that;                       // pointer for scoped context
        var args;                       // pointer for scoped arguments
        var result;                     // pointer for returned value of function
        var timeout = null;             // window Timeout reference
        var last_timestap = 0;          // date int
        // invoked function routine with flag cleanup
        var do_call = function()
        {
            // store returned values from invoked function
            result = fn.apply(that, args);
            // reset flags
            timeout = that = args = null;
        };
        // out-of scope routine for the scroll function routine while setting flags
        var call_me_maybe = function()
        {
            // set compared flags
            last_timestap = is_resize ? 0 : $.now();
            // invoke the function
            do_call();
        };
        return function bottle_neck(/*arguments*/)
        {
            // set current timestamp
            var now = $.now();
            if ( ! last_timestap && is_resize )
            {
                // set current timestamp
                last_timestap = now;
            }
            // get time left
            var remaining = delay - ( now - last_timestap );
            // set scope references
            that = this;
            args = arguments;

            // do now
            if ( remaining <= 0 )
            {
                clearTimeout( timeout );
                last_timestap = now;
                do_call();
            }
            // do later
            else if ( !timeout && !is_resize )
            {
                timeout = setTimeout( call_me_maybe, remaining );
            }
            return result;
        };
    };

    // wrap deferments of invocation around render function
    var do_scroll_browser_event = do_bottlenecked_event(do_render_view, 300);
    var do_resize_browser_event = do_bottlenecked_event(do_render_view, 250, true);

    // PURPOSE: load image and handle error and fail events
    // @param $el : $(DOMElement)
    // @param options : { options }
    // return undefined
    var do_load_image = function( $el, options )
    {
        var do_handle_error = function()
        {
            // add error className
            $el
                .removeClass( pending_classname + ' ' + options.successClass )
                .addClass( options.errorClass )
                ;

            // run error callback... with arguments and the selected context...
            if ( options.onError )
            {
                options.onError.apply( $el, arguments );
            }
        };

        var do_handle_sucess = function()
        {
            // NOTE: this logic allows usage on elements -- not just
            if ( $el.is( 'img' ) )
            {
                // set img.src property if element is an
                // set src prop and add success className
                $el.prop( 'src', data_source_val )
                    .removeClass( pending_classname + ' ' + options.errorClass )
                    .addClass( options.successClass )
                    ;
            }
            // set background-image style tag if element is something else like a
 or
            else
            {
                $el.css('background-image', 'url("' + data_source_val + '")');
            }

            // run success callback... with arguments and the selected context...
            if ( options.onSuccess )
            {
                options.onSuccess.apply( $el, arguments );
            }
        };

        // get the path to the img in an html data-* attr
        var data_source_val = $el.data( options.srcAttr ) || $el.data( default_attr_name );
        var img;

        if ( data_source_val )
        {
            // create element
            img = new Image();
            // set events
            img.onerror = do_handle_error;
            img.onload = do_handle_sucess;
            // load image
            img.src = data_source_val;
        }
        else
        {
            do_handle_error();
        }
    };

    // constants
    var $el_scrollable = $(window);
    // the private resize event namespace
    var resize_event_name = 'resize.holdup';
    // the private scroll event namespace
    var scroll_event_name = 'scroll.holdup';
    // the additional className to define image load has successfully completed -- static to keep render routine consistent
    var pending_classname = 'holdup-pending';
    // 'original' is the default to support lazyload's default markup expectations
    var default_attr_name = 'src';
    // boolean to determine if plugin has been initialized for plugin window event logic
    var is_initialized = false;

    // reference for holdup jQuery PLUGIN
    var old = $.fn.holdup;

    /* Holdup CONSTRUCTOR */
    // @param element : DOMElement
    // @param options : { options }
    var Holdup = function( element, options )
    {
        var that = this;
        // define this instance's set of options
        var these_options = that.options = $.extend( {}, Holdup.DEFAULTS, options );

        // save reference to element
        that.$el = $( element )
            // add base className & add waiting state className
            .addClass( these_options.baseClass+' '+pending_classname )
            // and placeholder img
            .prop( 'src', these_options.placeholder )
            ;

        // set property
        that.isloaded = false;

        // do instance show
        that.observe();
        that.render();
    };

    // public methods
    var HoldupProto = Holdup.prototype;

    // PURPOSE: force show a specific element
    HoldupProto.show = function()
    {
        do_load_image(this.$el, this.options);
    };

    HoldupProto.render = function()
    {
        // proceed if element is visible and not loaded
        if (!this.isloaded && is_element_in_view($el_scrollable, this.$el, this.options.threshold) )
        {
            this.show();
        }
    };

    // PURPOSE: add plugin window events
    HoldupProto.observe = function()
    {
        // avoids binding events too much
        if (!is_initialized)
        {
            // bind events
            $el_scrollable
                .on(scroll_event_name, do_scroll_browser_event)
                .on(resize_event_name, do_resize_browser_event)
                ;
            // set flag
            is_initialized = true;
        }
    };

    // PURPOSE: remove plugin window events
    HoldupProto.ignore = function()
    {
        // unbind
        $el_scrollable
            .off(scroll_event_name)
            .off(resize_event_name)
            ;
        // set flag
        is_initialized = false;
    };

    // define initial default options
    Holdup.DEFAULTS = {
        // the initialized className to define image load has been bound to element
        'baseClass': 'heldup',
        // the additional className to define image load has unsuccessfully completed
        'errorClass': 'heldup-error',
        // the additional className to define image load has successfully completed
        'successClass': 'heldup-success',
        // define callbacks to null
        // will execute when user adds option
        'onSuccess': null,
        'onError': null,
        // default placeholder for when the images are not loaded which is a 1x1 transparent gif
        'placeholder': 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
        // define slack of logic determining what' in the viewport
        'threshold': 0,
        // define the data attr to source the actual image -- if retina is found it toggles the default attr name
        'srcAttr': window.devicePixelRatio > 1 ? 'src-retina' : default_attr_name
    };

    // assign to {$.fn} namespace
    $.fn.holdup = function( option )
    {
        return this.each(function()
        {
            var $this = $( this );
            var data = $this.data( 'holdup' );
            var type = typeof option;

            // create new instance
            if ( !data )
            {
                $this.data( 'holdup', ( data = new Holdup( this, type === 'object' && option ) ) );
            }

            // execute instance method
            if ( type === 'string' && data[option] )
            {
                data[option]();
            }
        });
    };

    // set access to the plugin constructor to expose defaults and potentially allow DEFAULTS override...
    $.fn.holdup.Constructor = Holdup;

    /*
        // Holdup NO CONFLICT to reassign namespace
        **** USAGE
        var original_holdup = $.fn.holdup.noConflict()      // return $.fn.holdup to previously assigned value
        $.fn.someOtherNamespace = original_holdup           // give $().someOtherNamespace the yoholdup functionality
    */
    $.fn.holdup.noConflict = function()
    {
        $.fn.holdup = old;
        return this;
    };

    return $.fn.holdup;

}));
<commit_msg>un-needed jshint statement<commit_after>
/*!  jquery-holdup - */

/*  *** USAGE ***
    JavaScript:
        // these are the open or commonly used api methods
        $(selector).holdup({override_options})     // create instance
        $(selector).holdup('render')               // will determine if element should be shown
        $(selector).holdup('show')                 // will force elements to be loaded

        // these are exposed really for debugging purposes...
        $(selector).holdup('observe')              // will add container ( window ) event listeners if listeners have not been added
        $(selector).holdup('ignore')               // will remove container ( window ) event listeners

    HTML:
        // NOTE: based on DEFAULT options
        // src-retina path is for displays that support hi-dpi images

    CSS:
        // NOTE: based on DEFAULT options
        .heldup {
            rules ... rules
        }
        .heldup.heldup-success {
            rules ... rules
        }
        .heldup.heldup-error{
            rules ... rules
        }
*/
;( function ( factory )
{
    'use strict';

    if ( typeof define === 'function' && define.amd )
    {
        // AMD. Register as an anonymous module.
        define( ['jquery'], factory );
    }
    else
    {
        // Browser globals
        factory( jQuery );
    }
}
( function ( $ )
{
    'use strict';

    /* HELPER FUNCTIONS */
    // PURPOSE: check if element is in view to display
    // @param $container : $(DOMElement) of the scrollable view
    // @param $el : $(DOMElement)
    // @param threshold : Number
    // return bool
    var is_element_in_view = function( $container, $el, threshold )
    {
        // PURPOSE: check if element is in vertical viewport
        // @param el_top : Number
        // return bool
        var is_element_in_vertical_view = function( top )
        {
            var container_viewport_top = $container.scrollTop();
            return top + $el.height() >= container_viewport_top - threshold &&     // element bottom is within the viewport top
                top <= container_viewport_top + $container.height() + threshold    // element top is within the viewport bottom
                ;
        };

        // PURPOSE: check if element is in horizontal viewport
        // @param el_left : Number
        // return bool
        var is_element_in_horizontal_view = function( left )
        {
            var container_viewport_left = $container.scrollLeft();
            return left <= container_viewport_left + $container.width() &&         // element left is within the viewport right
                left + $el.width() >= container_viewport_left                      // element right is within the viewport left
                ;
        };

        // cache the element's coordinates on the page...
        var element_offset = $el.offset();

        return  is_element_in_vertical_view( element_offset.top ) &&
                is_element_in_horizontal_view( element_offset.left )
                ;
    };

    // PURPOSE: see if there are any more images to load -- see if rendering needs to be run
    // @param evt : $(Event)
    var do_render_view = function( evt )
    {
        // don't fire unless its by a human
        if ( evt.isTrigger )
        {
            return;
        }
        // selects the imgs waiting to be loaded
        var $imgs_not_loaded = $( '.' + pending_classname );

        // if there are images needing to load
        if ( $imgs_not_loaded.length )
        {
            // do the render method
            $imgs_not_loaded.holdup('render');
        }
        // or if the plugin observe has been initialized
        else if ( is_initialized )
        {
            // remove the events from the window
            HoldupProto.ignore();
        }
        // or nothing at all
    };

    // inspired by underscore's debounce and throttle routines
    var do_bottlenecked_event = function( fn, delay, is_resize )
    {
        var that;                       // pointer for scoped context
        var args;                       // pointer for scoped arguments
        var result;                     // pointer for returned value of function
        var timeout = null;             // window Timeout reference
        var last_timestap = 0;          // date int
        // invoked function routine with flag cleanup
        var do_call = function()
        {
            // store returned values from invoked function
            result = fn.apply(that, args);
            // reset flags
            timeout = that = args = null;
        };
        // out-of scope routine for the scroll function routine while setting flags
        var call_me_maybe = function()
        {
            // set compared flags
            last_timestap = is_resize ? 0 : $.now();
            // invoke the function
            do_call();
        };
        return function bottle_neck(/*arguments*/)
        {
            // set current timestamp
            var now = $.now();
            if ( ! last_timestap && is_resize )
            {
                // set current timestamp
                last_timestap = now;
            }
            // get time left
            var remaining = delay - ( now - last_timestap );
            // set scope references
            that = this;
            args = arguments;

            // do now
            if ( remaining <= 0 )
            {
                clearTimeout( timeout );
                last_timestap = now;
                do_call();
            }
            // do later
            else if ( !timeout && !is_resize )
            {
                timeout = setTimeout( call_me_maybe, remaining );
            }
            return result;
        };
    };

    // wrap deferments of invocation around render function
    var do_scroll_browser_event = do_bottlenecked_event(do_render_view, 300);
    var do_resize_browser_event = do_bottlenecked_event(do_render_view, 250, true);

    // PURPOSE: load image and handle error and fail events
    // @param $el : $(DOMElement)
    // @param options : { options }
    // return undefined
    var do_load_image = function( $el, options )
    {
        var do_handle_error = function()
        {
            // add error className
            $el
                .removeClass( pending_classname + ' ' + options.successClass )
                .addClass( options.errorClass )
                ;

            // run error callback... with arguments and the selected context...
            if ( options.onError )
            {
                options.onError.apply( $el, arguments );
            }
        };

        var do_handle_sucess = function()
        {
            // NOTE: this logic allows usage on elements -- not just
            if ( $el.is( 'img' ) )
            {
                // set img.src property if element is an
                // set src prop and add success className
                $el.prop( 'src', data_source_val )
                    .removeClass( pending_classname + ' ' + options.errorClass )
                    .addClass( options.successClass )
                    ;
            }
            // set background-image style tag if element is something else like a
 or
            else
            {
                $el.css('background-image', 'url("' + data_source_val + '")');
            }

            // run success callback... with arguments and the selected context...
            if ( options.onSuccess )
            {
                options.onSuccess.apply( $el, arguments );
            }
        };

        // get the path to the img in an html data-* attr
        var data_source_val = $el.data( options.srcAttr ) || $el.data( default_attr_name );
        var img;

        if ( data_source_val )
        {
            // create element
            img = new Image();
            // set events
            img.onerror = do_handle_error;
            img.onload = do_handle_sucess;
            // load image
            img.src = data_source_val;
        }
        else
        {
            do_handle_error();
        }
    };

    // constants
    var $el_scrollable = $(window);
    // the private resize event namespace
    var resize_event_name = 'resize.holdup';
    // the private scroll event namespace
    var scroll_event_name = 'scroll.holdup';
    // the additional className to define image load has successfully completed -- static to keep render routine consistent
    var pending_classname = 'holdup-pending';
    // 'original' is the default to support lazyload's default markup expectations
    var default_attr_name = 'src';
    // boolean to determine if plugin has been initialized for plugin window event logic
    var is_initialized = false;

    // reference for holdup jQuery PLUGIN
    var old = $.fn.holdup;

    /* Holdup CONSTRUCTOR */
    // @param element : DOMElement
    // @param options : { options }
    var Holdup = function( element, options )
    {
        var that = this;
        // define this instance's set of options
        var these_options = that.options = $.extend( {}, Holdup.DEFAULTS, options );

        // save reference to element
        that.$el = $( element )
            // add base className & add waiting state className
            .addClass( these_options.baseClass+' '+pending_classname )
            // and placeholder img
            .prop( 'src', these_options.placeholder )
            ;

        // set property
        that.isloaded = false;

        // do instance show
        that.observe();
        that.render();
    };

    // public methods
    var HoldupProto = Holdup.prototype;

    // PURPOSE: force show a specific element
    HoldupProto.show = function()
    {
        do_load_image(this.$el, this.options);
    };

    HoldupProto.render = function()
    {
        // proceed if element is visible and not loaded
        if (!this.isloaded && is_element_in_view($el_scrollable, this.$el, this.options.threshold) )
        {
            this.show();
        }
    };

    // PURPOSE: add plugin window events
    HoldupProto.observe = function()
    {
        // avoids binding events too much
        if (!is_initialized)
        {
            // bind events
            $el_scrollable
                .on(scroll_event_name, do_scroll_browser_event)
                .on(resize_event_name, do_resize_browser_event)
                ;
            // set flag
            is_initialized = true;
        }
    };

    // PURPOSE: remove plugin window events
    HoldupProto.ignore = function()
    {
        // unbind
        $el_scrollable
            .off(scroll_event_name)
            .off(resize_event_name)
            ;
        // set flag
        is_initialized = false;
    };

    // define initial default options
    Holdup.DEFAULTS = {
        // the initialized className to define image load has been bound to element
        'baseClass': 'heldup',
        // the additional className to define image load has unsuccessfully completed
        'errorClass': 'heldup-error',
        // the additional className to define image load has successfully completed
        'successClass': 'heldup-success',
        // define callbacks to null
        // will execute when user adds option
        'onSuccess': null,
        'onError': null,
        // default placeholder for when the images are not loaded which is a 1x1 transparent gif
        'placeholder': 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
        // define slack of logic determining what' in the viewport
        'threshold': 0,
        // define the data attr to source the actual image -- if retina is found it toggles the default attr name
        'srcAttr': window.devicePixelRatio > 1 ? 'src-retina' : default_attr_name
    };

    // assign to {$.fn} namespace
    $.fn.holdup = function( option )
    {
        return this.each(function()
        {
            var $this = $( this );
            var data = $this.data( 'holdup' );
            var type = typeof option;

            // create new instance
            if ( !data )
            {
                $this.data( 'holdup', ( data = new Holdup( this, type === 'object' && option ) ) );
            }

            // execute instance method
            if ( type === 'string' && data[option] )
            {
                data[option]();
            }
        });
    };

    // set access to the plugin constructor to expose defaults and potentially allow DEFAULTS override...
    $.fn.holdup.Constructor = Holdup;

    /*
        // Holdup NO CONFLICT to reassign namespace
        **** USAGE
        var original_holdup = $.fn.holdup.noConflict()      // return $.fn.holdup to previously assigned value
        $.fn.someOtherNamespace = original_holdup           // give $().someOtherNamespace the yoholdup functionality
    */
    $.fn.holdup.noConflict = function()
    {
        $.fn.holdup = old;
        return this;
    };

    return $.fn.holdup;

}));
<|endoftext|>


<commit_before>var observableArray = require("data/observable-array");
var pageModule = require("ui/page");
var viewModel = new observableArray.ObservableArray();
var viewModule = require("ui/core/view");
var gestures = require("ui/gestures");
var absoluteLayout = require("ui/layouts/absolute-layout");

viewModel.set("leftItems", [1, 2, 3, 4, 5 ]);
viewModel.set("rightItems", ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]);

exports.pageLoaded = function(args) {
    var page = args.object;
    viewModel.get("leftItems").push(6);
    viewModel.get("rightItems").push("k");
    page.bindingContext = viewModel;

    var dragLabel = viewModule.getViewById(page, "dragLabel");
    absoluteLayout.AbsoluteLayout.setLeft(dragLabel,50);
    absoluteLayout.AbsoluteLayout.setTop(dragLabel,50);
};

exports.dragit = function(args) {
  var view = args.view;
  var dragLabel = viewModule.getViewById(view.page, "dragLabel");
  dragLabel.visibility ="visible";
  dragLabel.text= "moving";

  var X = args.deltaX;
  var Y = args.deltaY;
  var left = absoluteLayout.AbsoluteLayout.getLeft(view);
  var top = absoluteLayout.AbsoluteLayout.getTop(view);
  absoluteLayout.AbsoluteLayout.setLeft(dragLabel,left+X);
  absoluteLayout.AbsoluteLayout.setTop(dragLabel,top+Y);

  console.log("dragit" + X + " " + Y);
  console.log("coord [" + top + " - " + top + "]");
};
<commit_msg>Update main-page.js<commit_after>var observableArray = require("data/observable-array");
var pageModule = require("ui/page");
var viewModel = new observableArray.ObservableArray();
var viewModule = require("ui/core/view");
var gestures = require("ui/gestures");
var absoluteLayout = require("ui/layouts/absolute-layout");

viewModel.set("leftItems", ["1", "2", "e", "4", "f" ]);
viewModel.set("rightItems", ["a", "3", "b", "c", "d", "5", "g", "h", "i", "j"]);

exports.pageLoaded = function(args) {
    var page = args.object;
    viewModel.get("leftItems").push("k");
    viewModel.get("rightItems").push("6");
    page.bindingContext = viewModel;

    var dragLabel = viewModule.getViewById(page, "dragLabel");
    absoluteLayout.AbsoluteLayout.setLeft(dragLabel,50);
    absoluteLayout.AbsoluteLayout.setTop(dragLabel,50);
};

exports.dragit = function(args) {
  var view = args.view;
  var dragLabel = viewModule.getViewById(view.page, "dragLabel");
  dragLabel.visibility ="visible";
  dragLabel.text= "moving";

  var X = args.deltaX;
  var Y = args.deltaY;
  var left = absoluteLayout.AbsoluteLayout.getLeft(view);
  var top = absoluteLayout.AbsoluteLayout.getTop(view);
  absoluteLayout.AbsoluteLayout.setLeft(dragLabel,left+X);
  absoluteLayout.AbsoluteLayout.setTop(dragLabel,top+Y);

  console.log("dragit" + X + " " + Y);
  console.log("coord [" + top + " - " + top + "]");
};
<|endoftext|>


<commit_before>'use strict'

//var roller = require('../lib/node-roll-master/bin/roll')

/** [TO DO] create rollers for:
 *
 * Cortex (multiple pools of die types; highlight best X results - usually 2)
 * Infinity (opposed roll under target # and closest to target # wins; critical if exactly target #)
 * Fate (special die type)
 *
 */

 /** Example dice codes
  *  ==================
  *
  * 3d6,2d8 ...........roll 2 pools of dice and return results
  * 8d10t>8s3..........roll 1 pool of dice and tell me if I get at least 3 dice showing an 8 or higher; return results
  * 7d6s2t>4!..........roll 1 pool of dice and tell me if I get at least 2 dice showing a 4 or higher; explode any dice that roll a 6; return results
  * 5d6b2..............roll 1 pool of dice and highlight the best 2 results
  * 4d6b3=.............roll 4 dice; keep the best 3 and total them
  */

module.exports = (app) => {
  let slapp = app.slapp
  let kv = app.dataStore

  //const fullDiceRegExp = /(\d*)([d](\d*)([\+|\-|\*|\/](\d*)))+/ig
  const diceRegExp = /(\d{1,}d[\d{1,}f])/i            // /((\d*)[d](\d*))+/i
  const fateDiceRegExp = /(\d{1,}df)/i
  const bestOrWorstResultsRegExp = /([bw](\d{1,}))/i
  const successRegExp = /(s\d{1,})/i
  const targetRegExp = /(t[><](\d{1,}))/i
  const modifierRegExp = /([\+\-\*\/]\d{1,})/ig
  const addModifierRegExp = /(\+\d{1,})/ig
  const subtractModifierRegExp = /(\-\d{1,})/ig
  const multiplyModifierRegExp = /(\*\d{1,})/ig
  const divideModifierRegExp = /(\/\d{1,})/ig

  const multiplePoolsSeparator = ','
  const explodeSymbol = '!'
  const sumResultsSymbol = '='
  const HighlightNoResults = 0
  const HighlightBestResults = 1
  const HighlightWorstResults = 2
  const contextSymbol = ':'

  const fateDiceSymbol = 'f'

  const MessageContextUser = 0
  const MessageContextCharacter = 1

  const diceRollIcon = 'https://d30y9cdsu7xlg0.cloudfront.net/png/10617-200.png'
  const diceRollMessageColor = '#ffb84d'
  const diceRollUser = 'RpgXDice'

  // Slash Command: ...
  slapp.command('/roll', (msg) => {

    var messageContext = MessageContextUser
    var commandParameters = null
    var command = null

    var results = []
    var rolls = []

    var username = diceRollUser
    var diceRollerName = msg.body.user_name
    var diceRollerThumb = diceRollIcon
    var diceRollerColor = diceRollMessageColor

    //    /(\w*)+[\|]/i

// =============================================================
    // ====  Get message context
    if (msg.body.text.indexOf(contextSymbol) != -1) {
      commandParameters = msg.body.text.split(contextSymbol)
      username = commandParameters[0]
      diceRollerName = commandParameters[0]
      command = commandParameters[1]
      messageContext = MessageContextCharacter
    } else {
      command = msg.body.text
    }

        // Get what dice pools we are rolling
    if (command.indexOf(multiplePoolsSeparator)) {
      rolls = command.split(multiplePoolsSeparator)
    } else {
      rolls.push(command)
    }

    // [DEBUG]
    console.info(`[DEBUG] rolls: ${rolls}`)
    // [TO DO] add error handling. If there are no dice to roll then we need to give feedback to the user and exit

    //
    for (var i = 0; i < rolls.length; i++) {

      // [DEBUG]
      console.log(`roll: ${rolls[i]}`)

      var diceArray = rolls[i].match(diceRegExp)
      var dice = "1d6"
      var quantityArray = null
      var quantity = 0
      var facesArray = null
      var faces = 0

      // [DEBUG]
      console.info(`[DEBUG] diceArray: ${diceArray}`)

      if (diceArray) {
        // [DEBUG]
        console.info(`[DEBUG] diceArray has value...`)

        quantityArray = diceArray[0].match(/^(\d{1,}d)/i)
        if (quantityArray) {
          // [DEBUG]
          console.info(`[DEBUG] quantityArray has value...`)
          quantity = quantityArray[0].match(/^(\d{1,})/)[0]
        }
        facesArray = diceArray[0].match(/(d[\d{1,}f])/i)
        if (facesArray) {
          // [DEBUG]
          console.info(`[DEBUG] facesArray has value...`)
          faces = facesArray[0].match(/([\d{1,}f])/i)[0]
        }
      }

      // See if we are using the Target number option
      var target = rolls[i].match(targetRegExp)
      if (target) { target = target[0].match(/[(\d*)]+/)[0] }

      // See if we are using the Successes required option
      var successesRequired = rolls[i].match(successRegExp)
      if (successesRequired) { successesRequired = successesRequired[0].match(/[(\d*)]+/)[0] }

      var modifiers = rolls[i].match(modifierRegExp)

      var resultsToHighlight = rolls[i].match(bestOrWorstResultsRegExp)
      var highlightType = HighlightNoResults
      if (resultsToHighlight) {
        if (resultsToHighlight[0].match(/(b)/i)) {
          highlightType = HighlightBestResults
        } else if (resultsToHighlight[0].match(/(w)/i)) {
          highlightType = HighlightWorstResults
        }
      }

      var bTotalResults = false
      if (rolls[i].indexOf(sumResultsSymbol) != -1) { bTotalResults = true }

      if (quantity > 0 && (faces > 0 || faces.toLowerCase() == fateDiceSymbol)) {
        results.push(diceRoll(quantity, faces, target, modifiers, successesRequired, bTotalResults))
      }
    }

    // [DEBUG]
      console.info(`[DEBUG] message context: ${messageContext}`)

    if(messageContext == MessageContextCharacter) {

      // [DEBUG]
      console.info(`[DEBUG] message context is CHARACTER`)

      // retrieve npc data if it exists
      kv.get(`${msg.body.user_id}::NPC::${diceRollerName}`, function (err, val) {

        if (!err) {
          if (val) {
            diceRollerThumb = val.thumb
            diceRollerColor = val.color

            // [DEBUG]
            console.info(`thumb: ${val.thumb} | color: ${val.color}`)

            for (var k = 0; k < results.length; k++) {
              //msg.say(`${msg.body.user_name} rolled: ${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`)

              // [DEBUG]
              console.info(`diceRollerThumb: ${diceRollerThumb} | diceRollerColor: ${diceRollerColor}`)

              msg.say({
                response_type: 'in_channel',
                username: `${username}   (@${msg.body.user_name})`,
                icon_url: `${diceRollerThumb}`,
                text: '',
                attachments: [{
                  text: `${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`,
                  //title: `${diceRollerName} rolled:`,
                  color: `${diceRollerColor}`,
                  mrkdwn_in: ["text", "pretext"],
                  thumb_url: `${diceRollIcon}`,

                  callback_id: 'rollDetails_callback',
                  actions: [{
                    name: 'rollDetails',
                    text: 'Details',
                    type: 'button',
                    value: 'SHOW_DETAILS'
                  }]
                }]
              })

              .thread()

              .say({
                response_type: 'in_channel',
                username: `${username}   (@${msg.body.user_name})`,
                icon_url: `${diceRollerThumb}`,
                text: '',
                attachments: [{
                text:
                  `Dice: ${results[k].quantity}d${results[k].faces}\n
                  Modifiers: ${results[k].modifiers} (*${results[k].modifier}*)\n
                  Roll(s): [${results[k].rolls}]\n
                  Roll Total: ${results[k].total} (*${results[k].modifiedTotal}*)`,
                  color: `${diceRollerColor}`,
                  mrkdwn_in: ["text", "pretext"],
                  thumb_url: `${diceRollIcon}`
                }]
              })

              //.unthread()
            }
          }  else {
            console.error(`[ERROR] no value for [${msg.body.user_id}::NPC::${diceRollerName}]`)
          }
        } else {
            // [TO DO] handle error
            console.error(`[ERROR] issue retrieving data for [${msg.body.user_id}::NPC::${diceRollerName}]`)
        }
      })
    } else {
      // [DEBUG]
      console.info(`[DEBUG] message context is USER`)

      for (var k = 0; k < results.length; k++) {
        //msg.say(`${msg.body.user_name} rolled: ${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`)

        // [DEBUG]
        console.info(`diceRollerThumb: ${diceRollerThumb} | diceRollerColor: ${diceRollerColor}`)

        msg.say({
          response_type: 'in_channel',
          username: `${username}   (@${msg.body.user_name})`,
          icon_url: `${diceRollerThumb}`,
          text: '',
          attachments: [{
            text: `${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`,
            title: `${diceRollerName} rolled:`,
            color: `${diceRollerColor}`,
            mrkdwn_in: ["text", "pretext"],
            thumb_url: `${diceRollIcon}`,

            callback_id: 'rollDetails_callback',
            actions: [{
              name: 'rollDetails',
              text: 'Details',
              type: 'button',
              value: 'SHOW_DETAILS'
            }]
          }]
        })

        // thread the next message containing the detailed output for the requested roll
        .thread()

        // roll details message
        .say({
          response_type: 'in_channel',
          username: `${username}   (@${msg.body.user_name})`,
          icon_url: `${diceRollerThumb}`,
          text: '',
          attachments: [{
            text:
              `Dice: ${results[k].quantity}d${results[k].faces}\n
              Modifiers: ${results[k].modifiers} (*${results[k].modifier}*)\n
              Roll(s): [${results[k].rolls}]\n
              Roll Total: ${results[k].total} (*${results[k].modifiedTotal}*)`,
            title: `${diceRollerName} rolled:`,
            color: `${diceRollerColor}`,
            mrkdwn_in: ["text", "pretext"],
            thumb_url: `${diceRollIcon}`
          }]
        })

        // set unthread to make sure other messages are not threaded
        // [TO DO] test to see if this is even needed
        //.unthread()
      }
    }
  })

  /**
   * ==========================================================================
   * diceRoll
   * ==========================================================================
   * @param {*} quantity
   * @param {*} faces
   * @param {*} target
   * @param {*} modifiers
   * @param {*} successesRequired
   */

  var diceRoll = function (quantity, faces, target, modifiers, successesRequired) {
    var poolResults = {
      quantity: quantity,
      faces: faces,
      modifiers: modifiers,
      modifier: 0,
      target: target,
      successesRequired: successesRequired,
      total: 0,
      modifiedTotal: 0,
      rolls: []
    }

    // [DEBUG]
    console.info(`[DEBUG] modifer list: ${modifiers}`)
    var modifier = 0
    if (modifiers) {
      for (var j = 0; j < modifiers.length; j++) {
        modifier += parseInt(modifiers[j])
        // [DEBUG]
        console.info(`[DEBUG] modifier: ${parseInt(modifiers[j])}`)
      }
      poolResults.modifier = modifier
    }

    // [DEBUG]
    console.info(`[DEBUG] modifier total: ${modifier}`)

    var rollResults = []
    var rollTotal = 0
    var floor = 1

    // fate dice
    if (faces.toLowerCase() == fateDiceSymbol) {
      faces = 3
      floor = -1
    }

    for (var i = 0; i < quantity; i++) {
      var roll = Math.floor(Math.random() * faces) + floor
      rollTotal += roll
      rollResults.push(roll)
    }

    poolResults.total = rollTotal;
    poolResults.modifiedTotal = rollTotal + modifier

    /** [TO DO]
     *
     * sum total; if required
     * get best or worst results; if required
     *
    */

    poolResults.rolls = rollResults

    return poolResults
  }

//=============================================================================
//=============================================================================

  slapp.action('rollDetails_callback', 'rollDetails', 'SHOW_DETAILS', (msg, val) => {
    console.info(`[DEBUG] SHOW_DETAILS action caught...`)

    msg.respond({
      replace_original: true,
      response_type: 'in_channel',
      username: msg.body.original_message.username,
      icon_url: msg.body.original_message.icon_url,
      text: msg.body.original_message.text,
      attachments: [{
        text: msg.body.original_message.attachments[0].text,
        //title: `${diceRollerName} rolled:`,
        color: msg.body.original_message.attachments[0].color,
        mrkdwn_in: ["text", "pretext"],
        thumb_url: `${diceRollIcon}`,

        callback_id: 'rollDetails_callback',
        actions: [
            {
                name: 'rollDetails',
                text: 'Summary',
                type: 'button',
                value: 'SHOW_SUMMARY'
            }
        ]
      }]
    })

  })

  slapp.action('rollDetails_callback', 'rollDetails', 'SHOW_SUMMARY', (msg, val) => {
    console.info(`[DEBUG] SHOW_SUMMARY action caught...`)

    msg.respond({
      replace_original: true,
      response_type: 'in_channel',
      username: msg.body.original_message.username,
      icon_url: msg.body.original_message.icon_url,
      text: msg.body.original_message.text,
      attachments: [{
        text: msg.body.original_message.attachments[0].text,
        //title: `${diceRollerName} rolled:`,
        color: msg.body.original_message.attachments[0].color,
        mrkdwn_in: ["text", "pretext"],
        thumb_url: `${diceRollIcon}`,

        callback_id: 'rollDetails_callback',
        actions: [
            {
                name: 'rollDetails',
                text: 'Details',
                type: 'button',
                value: 'SHOW_DETAILS'
            }
        ]

      }]
    })

  })
}<commit_msg>testing moving roll details to a threaded messsage off the main roll message<commit_after>'use strict'

//var roller = require('../lib/node-roll-master/bin/roll')

/** [TO DO] create rollers for:
 *
 * Cortex (multiple pools of die types; highlight best X results - usually 2)
 * Infinity (opposed roll under target # and closest to target # wins; critical if exactly target #)
 * Fate (special die type)
 *
 */

 /** Example dice codes
  *  ==================
  *
  * 3d6,2d8 ...........roll 2 pools of dice and return results
  * 8d10t>8s3..........roll 1 pool of dice and tell me if I get at least 3 dice showing an 8 or higher; return results
  * 7d6s2t>4!..........roll 1 pool of dice and tell me if I get at least 2 dice showing a 4 or higher; explode any dice that roll a 6; return results
  * 5d6b2..............roll 1 pool of dice and highlight the best 2 results
  * 4d6b3=.............roll 4 dice; keep the best 3 and total them
  */

module.exports = (app) => {
  let slapp = app.slapp
  let kv = app.dataStore

  //const fullDiceRegExp = /(\d*)([d](\d*)([\+|\-|\*|\/](\d*)))+/ig
  const diceRegExp = /(\d{1,}d[\d{1,}f])/i            // /((\d*)[d](\d*))+/i
  const fateDiceRegExp = /(\d{1,}df)/i
  const bestOrWorstResultsRegExp = /([bw](\d{1,}))/i
  const successRegExp = /(s\d{1,})/i
  const targetRegExp = /(t[><](\d{1,}))/i
  const modifierRegExp = /([\+\-\*\/]\d{1,})/ig
  const addModifierRegExp = /(\+\d{1,})/ig
  const subtractModifierRegExp = /(\-\d{1,})/ig
  const multiplyModifierRegExp = /(\*\d{1,})/ig
  const divideModifierRegExp = /(\/\d{1,})/ig

  const multiplePoolsSeparator = ','
  const explodeSymbol = '!'
  const sumResultsSymbol = '='
  const HighlightNoResults = 0
  const HighlightBestResults = 1
  const HighlightWorstResults = 2
  const contextSymbol = ':'

  const fateDiceSymbol = 'f'

  const MessageContextUser = 0
  const MessageContextCharacter = 1

  const diceRollIcon = 'https://d30y9cdsu7xlg0.cloudfront.net/png/10617-200.png'
  const diceRollMessageColor = '#ffb84d'
  const diceRollUser = 'RpgXDice'

  // Slash Command: ...
  slapp.command('/roll', (msg) => {

    var messageContext = MessageContextUser
    var commandParameters = null
    var command = null

    var results = []
    var rolls = []

    var username = diceRollUser
    var diceRollerName = msg.body.user_name
    var diceRollerThumb = diceRollIcon
    var diceRollerColor = diceRollMessageColor

    //    /(\w*)+[\|]/i

// =============================================================
    // ====  Get message context
    if (msg.body.text.indexOf(contextSymbol) != -1) {
      commandParameters = msg.body.text.split(contextSymbol)
      username = commandParameters[0]
      diceRollerName = commandParameters[0]
      command = commandParameters[1]
      messageContext = MessageContextCharacter
    } else {
      command = msg.body.text
    }

        // Get what dice pools we are rolling
    if (command.indexOf(multiplePoolsSeparator)) {
      rolls = command.split(multiplePoolsSeparator)
    } else {
      rolls.push(command)
    }

    // [DEBUG]
    console.info(`[DEBUG] rolls: ${rolls}`)
    // [TO DO] add error handling. If there are no dice to roll then we need to give feedback to the user and exit

    //
    for (var i = 0; i < rolls.length; i++) {

      // [DEBUG]
      console.log(`roll: ${rolls[i]}`)

      var diceArray = rolls[i].match(diceRegExp)
      var dice = "1d6"
      var quantityArray = null
      var quantity = 0
      var facesArray = null
      var faces = 0

      // [DEBUG]
      console.info(`[DEBUG] diceArray: ${diceArray}`)

      if (diceArray) {
        // [DEBUG]
        console.info(`[DEBUG] diceArray has value...`)

        quantityArray = diceArray[0].match(/^(\d{1,}d)/i)
        if (quantityArray) {
          // [DEBUG]
          console.info(`[DEBUG] quantityArray has value...`)
          quantity = quantityArray[0].match(/^(\d{1,})/)[0]
        }
        facesArray = diceArray[0].match(/(d[\d{1,}f])/i)
        if (facesArray) {
          // [DEBUG]
          console.info(`[DEBUG] facesArray has value...`)
          faces = facesArray[0].match(/([\d{1,}f])/i)[0]
        }
      }

      // See if we are using the Target number option
      var target = rolls[i].match(targetRegExp)
      if (target) { target = target[0].match(/[(\d*)]+/)[0] }

      // See if we are using the Successes required option
      var successesRequired = rolls[i].match(successRegExp)
      if (successesRequired) { successesRequired = successesRequired[0].match(/[(\d*)]+/)[0] }

      var modifiers = rolls[i].match(modifierRegExp)

      var resultsToHighlight = rolls[i].match(bestOrWorstResultsRegExp)
      var highlightType = HighlightNoResults
      if (resultsToHighlight) {
        if (resultsToHighlight[0].match(/(b)/i)) {
          highlightType = HighlightBestResults
        } else if (resultsToHighlight[0].match(/(w)/i)) {
          highlightType = HighlightWorstResults
        }
      }

      var bTotalResults = false
      if (rolls[i].indexOf(sumResultsSymbol) != -1) { bTotalResults = true }

      if (quantity > 0 && (faces > 0 || faces.toLowerCase() == fateDiceSymbol)) {
        results.push(diceRoll(quantity, faces, target, modifiers, successesRequired, bTotalResults))
      }
    }

    // [DEBUG]
      console.info(`[DEBUG] message context: ${messageContext}`)

    if(messageContext == MessageContextCharacter) {

      // [DEBUG]
      console.info(`[DEBUG] message context is CHARACTER`)

      // retrieve npc data if it exists
      kv.get(`${msg.body.user_id}::NPC::${diceRollerName}`, function (err, val) {

        if (!err) {
          if (val) {
            diceRollerThumb = val.thumb
            diceRollerColor = val.color

            // [DEBUG]
            console.info(`thumb: ${val.thumb} | color: ${val.color}`)

            for (var k = 0; k < results.length; k++) {
              //msg.say(`${msg.body.user_name} rolled: ${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`)

              // [DEBUG]
              console.info(`diceRollerThumb: ${diceRollerThumb} | diceRollerColor: ${diceRollerColor}`)

              msg.thread().say({
                response_type: 'in_channel',
                username: `${username}   (@${msg.body.user_name})`,
                icon_url: `${diceRollerThumb}`,
                text: '',
                attachments: [{
                  text: `${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`,
                  //title: `${diceRollerName} rolled:`,
                  color: `${diceRollerColor}`,
                  mrkdwn_in: ["text", "pretext"],
                  thumb_url: `${diceRollIcon}`,

                  callback_id: 'rollDetails_callback',
                  actions: [{
                    name: 'rollDetails',
                    text: 'Details',
                    type: 'button',
                    value: 'SHOW_DETAILS'
                  }]
                }]
              })

              //.thread()

              .say({
                response_type: 'in_channel',
                username: `${username}   (@${msg.body.user_name})`,
                icon_url: `${diceRollerThumb}`,
                text: '',
                attachments: [{
                text:
                  `Dice: ${results[k].quantity}d${results[k].faces}\n
                  Modifiers: ${results[k].modifiers} (*${results[k].modifier}*)\n
                  Roll(s): [${results[k].rolls}]\n
                  Roll Total: ${results[k].total} (*${results[k].modifiedTotal}*)`,
                  color: `${diceRollerColor}`,
                  mrkdwn_in: ["text", "pretext"],
                  thumb_url: `${diceRollIcon}`
                }]
              })

              //.unthread()
            }
          }  else {
            console.error(`[ERROR] no value for [${msg.body.user_id}::NPC::${diceRollerName}]`)
          }
        } else {
            // [TO DO] handle error
            console.error(`[ERROR] issue retrieving data for [${msg.body.user_id}::NPC::${diceRollerName}]`)
        }
      })
    } else {
      // [DEBUG]
      console.info(`[DEBUG] message context is USER`)

      for (var k = 0; k < results.length; k++) {
        //msg.say(`${msg.body.user_name} rolled: ${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`)

        // [DEBUG]
        console.info(`diceRollerThumb: ${diceRollerThumb} | diceRollerColor: ${diceRollerColor}`)

        msg.thread().say({
          response_type: 'in_channel',
          username: `${username}   (@${msg.body.user_name})`,
          icon_url: `${diceRollerThumb}`,
          text: '',
          attachments: [{
            text: `${results[k].quantity}d${results[k].faces} [${results[k].rolls}] (*${results[k].modifiedTotal}*)`,
            title: `${diceRollerName} rolled:`,
            color: `${diceRollerColor}`,
            mrkdwn_in: ["text", "pretext"],
            thumb_url: `${diceRollIcon}`,

            callback_id: 'rollDetails_callback',
            actions: [{
              name: 'rollDetails',
              text: 'Details',
              type: 'button',
              value: 'SHOW_DETAILS'
            }]
          }]
        })

        // thread the next message containing the detailed output for the requested roll
        //.thread()

        // roll details message
        .say({
          response_type: 'in_channel',
          username: `${username}   (@${msg.body.user_name})`,
          icon_url: `${diceRollerThumb}`,
          text: '',
          attachments: [{
            text:
              `Dice: ${results[k].quantity}d${results[k].faces}\n
              Modifiers: ${results[k].modifiers} (*${results[k].modifier}*)\n
              Roll(s): [${results[k].rolls}]\n
              Roll Total: ${results[k].total} (*${results[k].modifiedTotal}*)`,
            title: `${diceRollerName} rolled:`,
            color: `${diceRollerColor}`,
            mrkdwn_in: ["text", "pretext"],
            thumb_url: `${diceRollIcon}`
          }]
        })

        // set unthread to make sure other messages are not threaded
        // [TO DO] test to see if this is even needed
        //.unthread()
      }
    }
  })

  /**
   * ==========================================================================
   * diceRoll
   * ==========================================================================
   * @param {*} quantity
   * @param {*} faces
   * @param {*} target
   * @param {*} modifiers
   * @param {*} successesRequired
   */

  var diceRoll = function (quantity, faces, target, modifiers, successesRequired) {
    var poolResults = {
      quantity: quantity,
      faces: faces,
      modifiers: modifiers,
      modifier: 0,
      target: target,
      successesRequired: successesRequired,
      total: 0,
      modifiedTotal: 0,
      rolls: []
    }

    // [DEBUG]
    console.info(`[DEBUG] modifer list: ${modifiers}`)
    var modifier = 0
    if (modifiers) {
      for (var j = 0; j < modifiers.length; j++) {
        modifier += parseInt(modifiers[j])
        // [DEBUG]
        console.info(`[DEBUG] modifier: ${parseInt(modifiers[j])}`)
      }
      poolResults.modifier = modifier
    }

    // [DEBUG]
    console.info(`[DEBUG] modifier total: ${modifier}`)

    var rollResults = []
    var rollTotal = 0
    var floor = 1

    // fate dice
    if (faces.toLowerCase() == fateDiceSymbol) {
      faces = 3
      floor = -1
    }

    for (var i = 0; i < quantity; i++) {
      var roll = Math.floor(Math.random() * faces) + floor
      rollTotal += roll
      rollResults.push(roll)
    }

    poolResults.total = rollTotal;
    poolResults.modifiedTotal = rollTotal + modifier

    /** [TO DO]
     *
     * sum total; if required
     * get best or worst results; if required
     *
    */

    poolResults.rolls = rollResults

    return poolResults
  }

//=============================================================================
//=============================================================================

  slapp.action('rollDetails_callback', 'rollDetails', 'SHOW_DETAILS', (msg, val) => {
    console.info(`[DEBUG] SHOW_DETAILS action caught...`)

    msg.respond({
      replace_original: true,
      response_type: 'in_channel',
      username: msg.body.original_message.username,
      icon_url: msg.body.original_message.icon_url,
      text: msg.body.original_message.text,
      attachments: [{
        text: msg.body.original_message.attachments[0].text,
        //title: `${diceRollerName} rolled:`,
        color: msg.body.original_message.attachments[0].color,
        mrkdwn_in: ["text", "pretext"],
        thumb_url: `${diceRollIcon}`,

        callback_id: 'rollDetails_callback',
        actions: [
            {
                name: 'rollDetails',
                text: 'Summary',
                type: 'button',
                value: 'SHOW_SUMMARY'
            }
        ]
      }]
    })

  })

  slapp.action('rollDetails_callback', 'rollDetails', 'SHOW_SUMMARY', (msg, val) => {
    console.info(`[DEBUG] SHOW_SUMMARY action caught...`)

    msg.respond({
      replace_original: true,
      response_type: 'in_channel',
      username: msg.body.original_message.username,
      icon_url: msg.body.original_message.icon_url,
      text: msg.body.original_message.text,
      attachments: [{
        text: msg.body.original_message.attachments[0].text,
        //title: `${diceRollerName} rolled:`,
        color: msg.body.original_message.attachments[0].color,
        mrkdwn_in: ["text", "pretext"],
        thumb_url: `${diceRollIcon}`,

        callback_id: 'rollDetails_callback',
        actions: [
            {
                name: 'rollDetails',
                text: 'Details',
                type: 'button',
                value: 'SHOW_DETAILS'
            }
        ]

      }]
    })

  })
}<|endoftext|>