<commit_before>/* Copyright (c) 2012-2013 The TagSpaces Authors. All rights reserved.
 * Use of this source code is governed by a AGPL3 license that
 * can be found in the LICENSE file. */
define(function (require, exports, module) {
    "use strict";

    console.log("Loading ioapi.cordova.js..");

    var TSCORE = require("tscore");

    var TSPOSTIO = require("tspostioapi");

    var fsRoot = undefined;

    document.addEventListener("deviceready", onDeviceReady, false);

    // Cordova loaded and can be used
    function onDeviceReady() {
        console.log("Devive Ready: "+device.platform+" - "+device.version);
        getFileSystem();
    }

    function getFileSystem() {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
            function (fileSystem) { // success get file system
                fsRoot = fileSystem.root;
                console.log("Filesystem Name: " + fsRoot.fullPath);
            },
            function (evt) { // error get file system
                console.log("File System Error: " + evt.target.error.code);
            }
        );
    }

    function scanDirectory(dirPath, index) {
        // TODO recursivly calling callback not working
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);
        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        for (i = 0; i < entries.length; i++) {
                            index.push({
                                "name": entries[i].name,
                                "type": entries[i].isFile?"file":"directory",
                                "size": "0",
                                "lmdt": "0",
                                "path": entries[i].fullPath
                            });
                            if (entries[i].isDirectory) {
                                scanDirectory(entries[i].fullPath, index);
                            }
                        }
                        return index;
                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    function generateDirectoryTree(dirPath) {
        // TODO recursivly calling callback not working
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);
        var tree = {};
        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        tree["name"] = dirEntry.name;
                        tree["type"] = "directory";
                        tree["lmdt"] = 0;
                        tree["path"] = dirEntry.fullPath;
                        tree["children"] = [];
                        for (var i=0; i < entries.length; i++) {
                            if (entries[i].isFile) {
                                tree["children"].push({
                                    "name": entries[i].name,
                                    "type": "file",
                                    "size": "0",
                                    "lmdt": "0",
                                    "path": entries[i].fullPath
                                });
                            } else {
                                tree["children"].push( generateDirectoryTree(entries[i].fullPath) );
                            }
                        }
                        return tree;
                    },
                    function (error) { // error get file system
                        console.log("Dir Tree Creation Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    function isWindows() {
        return (navigator.platform == 'Win32');
    }

    function getDirseparator() {
        if(isWindows()) {
            return "\\";
        } else {
            return "/";
        }
    }

    function normalizePath(path) {
        if(path.indexOf(fsRoot.fullPath) >= 0) {
            path = path.substring(fsRoot.fullPath.length+1, path.length);
        }
        return path;
    }

    exports.checkNewVersion = function() {
        console.log("Checking for new version...");
        var cVer = TSCORE.Config.DefaultSettings["appVersion"]+"."+TSCORE.Config.DefaultSettings["appBuild"];
        $.ajax({
            url: 'http://tagspaces.org/releases/version.json?pVer='+cVer,
            type: 'GET',
        })
        .done(function(data) {
            TSPOSTIO.checkNewVersion(data);
        })
        .fail(function(data) {
            console.log("AJAX failed "+data);
        })
        ;
    }

    exports.listDirectory = function (dirPath) {
        // directory path format DCIM/Camera/ !
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);

        console.log("Listing directory: " + dirPath);

        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();

                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        var anotatedDirList = [];
                        for (i = 0; i < entries.length; i++) {
                            console.log("File: "+entries[i].name);
                            anotatedDirList.push({
                                "name": entries[i].name,
                                "type": entries[i].isFile?"file":"directory",
                                "size": "0",
                                "lmdt": "0",
                                "path": entries[i].fullPath
                            });
                        }
                        //console.log("Dir content: " + JSON.stringify(entries));
                        TSPOSTIO.listDirectory(anotatedDirList);
                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    exports.getSubdirs = function(dirPath) {
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);

        console.log("Getting subdirs: "+dirPath);

        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        var anotatedDirList = [];
                        for (i = 0; i < entries.length; i++) {
                            console.log(entries[i].name);
                            if(entries[i].isDirectory) {
                                anotatedDirList.push({
                                    "title": entries[i].name,
                                    "isFolder": true,
                                    "key": entries[i].fullPath
                                });
                            }
                        }
                        console.log("Subdir content: " + JSON.stringify(entries));
                        TSPOSTIO.getSubdirs(anotatedDirList);

                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    exports.deleteElement = function(path) {
        console.log("Deleting: "+path);

        path = normalizePath(path);

        fsRoot.getFile(path, {create: false, exclusive: false},
            function(entry) {
                entry.remove(
                    function() {
                        console.log("file deleted: "+path);
                    },
                    function() {
                        console.log("error deleting: "+path);
                    }
                );
            },
            function() {
                console.log("error getting file");
            }
        );
    }

    exports.createDirectoryIndex = function(dirPath) {
        console.log("Creating index for directory: "+dirPath);
        var directoryIndex = [];
        directoryIndex = scanDirectory(dirPath, directoryIndex);
        //console.log(JSON.stringify(directoryIndex));
        TSPOSTIO.createDirectoryIndex(directoryIndex);
    }

    exports.createDirectoryTree = function(dirPath) {
        console.log("Creating directory index for: "+dirPath);
        var directoyTree = generateDirectoryTree(dirPath);
        //console.log(JSON.stringify(directoyTree));
        TSPOSTIO.createDirectoryTree(directoyTree);
    }

    exports.loadTextFile = function(filePath) {
        filePath = normalizePath(filePath);
        console.log("Loading file: "+filePath);
        fsRoot.getFile(filePath, {create: false, exclusive: false},
            function(entry) {
                entry.file(
                    function(file) {
                        var reader = new FileReader();
                        reader.onloadend = function(evt) {
                            TSPOSTIO.loadTextFile(evt.target.result);
                        };
                        reader.readAsText(file);
                    },
                    function() {
                        console.log("error getting file: "+filePath);
                    }
                );
            },
            function() {
                console.log("Error getting file entry: "+filePath);
            }
        );
    }

    exports.saveTextFile = function(filePath,content) {
        filePath = normalizePath(filePath);
        console.log("Saving file: "+filePath);
        fsRoot.getFile(filePath, {create: true, exclusive: false},
            function(entry) {
                entry.createWriter(
                    function(writer) {
                        writer.onwriteend = function(evt) {
                            TSPOSTIO.saveTextFile(fsRoot.fullPath+"/"+filePath);
                        };
                        writer.write(content);
                    },
                    function() {
                        console.log("error creating writter file: "+filePath);
                    }
                );
            },
            function() {
                console.log("Error getting file entry: "+filePath);
            }
        );
    }

    exports.createDirectory = function(dirPath) {
        dirPath = normalizePath(dirPath);
        console.log("Creating directory: "+dirPath);
        fsRoot.getDirectory(dirPath, {create: true, exclusive: false},
           function (dirEntry) {
                TSPOSTIO.createDirectory();
           },
           function (error) {
                console.log("Creating directory failed: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    exports.renameFile = function(filePath, newFilePath) {
        filePath = normalizePath(filePath);
        var newFileName = newFilePath.substring(newFilePath.lastIndexOf('/')+1);
        var newFileParentPath = normalizePath(newFilePath.substring(0, newFilePath.lastIndexOf('/')));

        fsRoot.getDirectory(newFileParentPath, {create: false, exclusive: false},
            function (parentDirEntry) {
                fsRoot.getFile(filePath, {create: false, exclusive: false},
                    function(entry) {
                        entry.moveTo(
                            parentDirEntry,
                            newFileName,
                            function() {
                                console.log("file renamed to: "+newFilePath);
                                TSPOSTIO.renameFile(filePath, newFilePath);
                            },
                            function() {
                                console.log("error renaming: "+filePath);
                            }
                        );
                    },
                    function() {
                        console.log("Error getting file: "+filePath);
                    }
                );
           },
           function (error) {
                console.log("Getting dir: "+newFileParentPath+" failed with error code: " + error.code);
           }
        );
    }

    exports.selectDirectory = function() {
        console.log("Operation selectDirectory not supported on Android yet!");
        TSCORE.showAlertDialog("Selecting directory not supported on Android yet, please type the desired directory path manually in textbox!");
    }

    exports.selectFile = function() {
        console.log("Operation selectFile not supported on Android!");
    }

    exports.checkAccessFileURLAllowed = function() {
        console.log("checkAccessFileURLAllowed function not relevant for android..");
    }

    exports.openDirectory = function(dirPath) {
        TSCORE.showAlertDialog("Select file functionality not supported on Android!");
    }

    exports.openExtensionsDirectory = function() {
        TSCORE.showAlertDialog("Open extensions directory functionality not supported on Android!");
    }
});<commit_msg>Adding messages for supported functionalities in cordova<commit_after>/* Copyright (c) 2012-2013 The TagSpaces Authors. All rights reserved.
 * Use of this source code is governed by a AGPL3 license that
 * can be found in the LICENSE file. */
define(function (require, exports, module) {
    "use strict";

    console.log("Loading ioapi.cordova.js..");

    var TSCORE = require("tscore");

    var TSPOSTIO = require("tspostioapi");

    var fsRoot = undefined;

    document.addEventListener("deviceready", onDeviceReady, false);

    // Cordova loaded and can be used
    function onDeviceReady() {
        console.log("Devive Ready: "+device.platform+" - "+device.version);
        getFileSystem();
    }

    function getFileSystem() {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
            function (fileSystem) { // success get file system
                fsRoot = fileSystem.root;
                console.log("Filesystem Name: " + fsRoot.fullPath);
            },
            function (evt) { // error get file system
                console.log("File System Error: " + evt.target.error.code);
            }
        );
    }

    // TODO recursivly calling callback not working
    function scanDirectory(dirPath, index) {
        fsRoot.getDirectory(normalizePath(dirPath+"/"), {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        var recursed = false;
                        for (i = 0; i < entries.length; i++) {
                            index.push({
                                "name": entries[i].name,
                                "type": entries[i].isFile?"file":"directory",
                                "size": "0",
                                "lmdt": "0",
                                "path": entries[i].fullPath
                            });
                            if (entries[i].isDirectory) {
                                recursed = true;
                                scanDirectory(entries[i].fullPath, index);
                            }
                        }
                        if (!recursed) return index;
                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    function generateDirectoryTree(dirPath) {
        // TODO recursivly calling callback not working
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);
        var tree = {};
        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        tree["name"] = dirEntry.name;
                        tree["type"] = "directory";
                        tree["lmdt"] = 0;
                        tree["path"] = dirEntry.fullPath;
                        tree["children"] = [];
                        for (var i=0; i < entries.length; i++) {
                            if (entries[i].isFile) {
                                tree["children"].push({
                                    "name": entries[i].name,
                                    "type": "file",
                                    "size": "0",
                                    "lmdt": "0",
                                    "path": entries[i].fullPath
                                });
                            } else {
                                tree["children"].push( generateDirectoryTree(entries[i].fullPath) );
                            }
                        }
                        return tree;
                    },
                    function (error) { // error get file system
                        console.log("Dir Tree Creation Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    }

    function isWindows() {
        return (navigator.platform == 'Win32');
    }

    function getDirseparator() {
        if(isWindows()) {
            return "\\";
        } else {
            return "/";
        }
    }

    function normalizePath(path) {
        if(path.indexOf(fsRoot.fullPath) >= 0) {
            path = path.substring(fsRoot.fullPath.length+1, path.length);
        }
        return path;
    }

    exports.checkNewVersion = function() {
        console.log("Checking for new version...");
        var cVer = TSCORE.Config.DefaultSettings["appVersion"]+"."+TSCORE.Config.DefaultSettings["appBuild"];
        $.ajax({
            url: 'http://tagspaces.org/releases/version.json?pVer='+cVer,
            type: 'GET',
        })
        .done(function(data) {
            TSPOSTIO.checkNewVersion(data);
        })
        .fail(function(data) {
            console.log("AJAX failed "+data);
        })
        ;
    };

    exports.listDirectory = function (dirPath) {
        // directory path format DCIM/Camera/ !
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);

        console.log("Listing directory: " + dirPath);

        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();

                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        var anotatedDirList = [];
                        for (i = 0; i < entries.length; i++) {
                            console.log("File: "+entries[i].name);
                            anotatedDirList.push({
                                "name": entries[i].name,
                                "type": entries[i].isFile?"file":"directory",
                                "size": "0",
                                "lmdt": "0",
                                "path": entries[i].fullPath
                            });
                        }
                        //console.log("Dir content: " + JSON.stringify(entries));
                        TSPOSTIO.listDirectory(anotatedDirList);
                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    };

    exports.getSubdirs = function(dirPath) {
        dirPath = dirPath+"/"; // TODO make it platform independent
        dirPath = normalizePath(dirPath);

        console.log("Getting subdirs: "+dirPath);

        fsRoot.getDirectory(dirPath, {create: false, exclusive: false},
            function (dirEntry) {
                var directoryReader = dirEntry.createReader();
                // Get a list of all the entries in the directory
                directoryReader.readEntries(
                    function (entries) {
                        var i;
                        var anotatedDirList = [];
                        for (i = 0; i < entries.length; i++) {
                            console.log(entries[i].name);
                            if(entries[i].isDirectory) {
                                anotatedDirList.push({
                                    "title": entries[i].name,
                                    "isFolder": true,
                                    "key": entries[i].fullPath
                                });
                            }
                        }
                        console.log("Subdir content: " + JSON.stringify(entries));
                        TSPOSTIO.getSubdirs(anotatedDirList);

                    }, function (error) { // error get file system
                        console.log("Dir List Error: " + error.code);
                    }
               );
           },
           function (error) {
                console.log("Getting dir: "+dirPath+" failed with error code: " + error.code);
           }
        );
    };

    exports.deleteElement = function(path) {
        console.log("Deleting: "+path);

        path = normalizePath(path);

        fsRoot.getFile(path, {create: false, exclusive: false},
            function(entry) {
                entry.remove(
                    function() {
                        console.log("file deleted: "+path);
                    },
                    function() {
                        console.log("error deleting: "+path);
                    }
                );
            },
            function() {
                console.log("error getting file");
            }
        );
    };

    exports.createDirectoryIndex = function(dirPath) {
        console.log("Creating index for directory: "+dirPath);
        TSCORE.showAlertDialog("Creating directory index is not supported on Android yet.");
/*        var directoryIndex = [];
        directoryIndex = scanDirectory(dirPath, directoryIndex);
        console.log(JSON.stringify(directoryIndex));
        TSPOSTIO.createDirectoryIndex(directoryIndex); */
    };

    exports.createDirectoryTree = function(dirPath) {
        console.log("Creating directory index for: "+dirPath);
        TSCORE.showAlertDialog("Creating directory tree is not supported on Android yet.");
/*        var directoyTree = generateDirectoryTree(dirPath);
        //console.log(JSON.stringify(directoyTree));
        TSPOSTIO.createDirectoryTree(directoyTree);*/
    };

    exports.loadTextFile = function(filePath) {
        filePath = normalizePath(filePath);
        console.log("Loading file: "+filePath);
        fsRoot.getFile(filePath, {create: false, exclusive: false},
            function(entry) {
                entry.file(
                    function(file) {
                        var reader = new FileReader();
                        reader.onloadend = function(evt) {
                            TSPOSTIO.loadTextFile(evt.target.result);
                        };
                        reader.readAsText(file);
                    },
                    function() {
                        console.log("error getting file: "+filePath);
                    }
                );
            },
            function() {
                console.log("Error getting file entry: "+filePath);
            }
        );
    };

    exports.saveTextFile = function(filePath,content) {
        filePath = normalizePath(filePath);
        console.log("Saving file: "+filePath);
        fsRoot.getFile(filePath, {create: true, exclusive: false},
            function(entry) {
                entry.createWriter(
                    function(writer) {
                        writer.onwriteend = function(evt) {
                            TSPOSTIO.saveTextFile(fsRoot.fullPath+"/"+filePath);
                        };
                        writer.write(content);
                    },
                    function() {
                        console.log("error creating writter file: "+filePath);
                    }
                );
            },
            function() {
                console.log("Error getting file entry: "+filePath);
            }
        );
    };

    exports.createDirectory = function(dirPath) {
        dirPath = normalizePath(dirPath);
        console.log("Creating directory: "+dirPath);
        fsRoot.getDirectory(dirPath, {create: true, exclusive: false},
           function (dirEntry) {
                TSPOSTIO.createDirectory();
           },
           function (error) {
                console.log("Creating directory failed: "+dirPath+" failed with error code: " + error.code);
           }
        );
    };

    exports.renameFile = function(filePath, newFilePath) {
        filePath = normalizePath(filePath);
        var newFileName = newFilePath.substring(newFilePath.lastIndexOf('/')+1);
        var newFileParentPath = normalizePath(newFilePath.substring(0, newFilePath.lastIndexOf('/')));
        // TODO check if the newFilePath exist or cause issues by renaming
        fsRoot.getDirectory(newFileParentPath, {create: false, exclusive: false},
            function (parentDirEntry) {
                fsRoot.getFile(filePath, {create: false, exclusive: false},
                    function(entry) {
                        entry.moveTo(
                            parentDirEntry,
                            newFileName,
                            function() {
                                console.log("file renamed to: "+newFilePath);
                                TSPOSTIO.renameFile(filePath, newFilePath);
                            },
                            function() {
                                console.log("error renaming: "+filePath);
                            }
                        );
                    },
                    function() {
                        console.log("Error getting file: "+filePath);
                    }
                );
           },
           function (error) {
                console.log("Getting dir: "+newFileParentPath+" failed with error code: " + error.code);
           }
        );
    };

    exports.selectDirectory = function() {
        console.log("Operation selectDirectory not supported on Android yet!");
        TSCORE.showAlertDialog("Selecting directory not supported on Android yet, please type the desired directory path manually in textbox!");
    };

    exports.selectFile = function() {
        console.log("Operation selectFile not supported on Android!");
    };

    exports.checkAccessFileURLAllowed = function() {
        console.log("checkAccessFileURLAllowed function not relevant for android..");
    };

    exports.openDirectory = function(dirPath) {
        TSCORE.showAlertDialog("Select file functionality not supported on Android!");
    };

    exports.openExtensionsDirectory = function() {
        TSCORE.showAlertDialog("Open extensions directory functionality not supported on Android!");
    };
});<|endoftext|>


<commit_before>/**
 * @file changelog command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

exports.exec = (Bastion, message) => {
  const CHANGES = require('../../changes.json');

  let changes = [];
  for (let section in CHANGES) {
    if (CHANGES.hasOwnProperty(section)) {
      if (section === 'date' || section === 'image' || !CHANGES[section].length) continue;

      changes.push({
        name: section,
        value: `- ${CHANGES[section].join('\n- ')}`
      });
    }
  }

  changes.push(
    {
      name: '\u200B',
      value: '\u200B'
    },
    {
      name: 'Missed an update?',
      value: '[Check out our previous change logs](https://github.com/TheBastionBot/Bastion/releases).'
        + '\nJoin **Bastion HQ** and never miss an update: https://discord.gg/fzx8fkt'
    },
    {
      name: 'Let\'s Connect...',
      value: 'Connect with Bastion on [Twitter](https://twitter.com/TheBastionBot) and [Reddit](https://reddit.com/r/TheBastionBot).'
    },
    {
      name: 'Support Bastion\'s Development',
      value: '[Support the development of Bastion](https://bastionbot.org/donate) to keep it running forever and get cool rewards!'
    }
  );

  message.channel.send({
    embed: {
      color: Bastion.colors.BLUE,
      title: `Bastion Bot v${Bastion.package.version} Changelog`,
      url: `https://github.com/TheBastionBot/Bastion/releases/v${Bastion.package.version}`,
      fields: changes,
      image: {
        url: CHANGES.image
      },
      footer: {
        text: CHANGES.date
      }
    }
  }).catch(e => {
    Bastion.log.error(e);
  });
};

exports.config = {
  aliases: [ 'clog', 'changes' ],
  enabled: true
};

exports.help = {
  name: 'changelog',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'changelog',
  example: []
};
<commit_msg>commands: minor changes to changelog command<commit_after>/**
 * @file changelog command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

exports.exec = (Bastion, message) => {
  const CHANGES = require('../../changes.json');

  let changes = [];
  for (let section in CHANGES) {
    if (CHANGES.hasOwnProperty(section)) {
      if (section === 'date' || section === 'image' || !CHANGES[section].length) continue;

      changes.push({
        name: section,
        value: `- ${CHANGES[section].join('\n- ')}`
      });
    }
  }

  changes.push(
    {
      name: '\u200B',
      value: '\u200B'
    },
    {
      name: 'Missed an update?',
      value: '[Check out our previous change logs](https://github.com/TheBastionBot/Bastion/releases).'
        + '\nJoin **Bastion HQ** and never miss an update: https://discord.gg/fzx8fkt'
    },
    {
      name: 'Loving Bastion?',
      value: 'Then why wait? Go ahead and express your feelings by tweeting him [@TheBastionBot](https://twitter.com/TheBastionBot) and your testimonial will be posted in [our testimonials page](https://bastionbot.org/testimonials).'
    },
    {
      name: 'Support Bastion\'s Development',
      value: '[Support the development of Bastion](https://bastionbot.org/donate) to keep it running forever and get cool rewards!'
    }
  );

  message.channel.send({
    embed: {
      color: Bastion.colors.BLUE,
      title: `Bastion Bot v${Bastion.package.version} Changelog`,
      url: `https://github.com/TheBastionBot/Bastion/releases/v${Bastion.package.version}`,
      fields: changes,
      image: {
        url: CHANGES.image
      },
      footer: {
        text: CHANGES.date
      }
    }
  }).catch(e => {
    Bastion.log.error(e);
  });
};

exports.config = {
  aliases: [ 'clog', 'changes' ],
  enabled: true
};

exports.help = {
  name: 'changelog',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'changelog',
  example: []
};
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/* global $, Config, jQuery, sakai, sdata */

require(["jquery","sakai/sakai.api.core", "myb/myb.api.core", "/dev/javascript/myb/myb.securepage.js"], function($, sakai, myb) {
    /**
     * @name sakai_global.dynamiclistmanager
     *
     * @class dynamiclistmanager
     *
     * @description
     * Dynamic list manager widget
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.dynamiclistmanager = function(tuid, showSettings) {

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        /**
         * Hashtable of all the lists
         */
        var allLists = {};

        /**
         * Dynamic lists base URL (parent node in Sparse), delete this node to remove all dynamic lists for current user
         */
        var dynamicListsBaseUrl;

        //////////////////////
        // jQuery selectors //
        //////////////////////

        /**
         * Widget's root element
         */
        var $rootElement = $("#" + tuid);

        /**
         * Dynamic list 'Edit' button
         */
        var $dynListsEditButton = $("#dyn_lists_edit_button");

        /**
         * Dynamic list 'Copy' button
         */
        var $dynListsCopyButton = $("#dyn_lists_copy_button");

        /**
         * Dynamic list 'delete' button
         */
        var $dynListsDeleteButton = $("#dyn_lists_delete_button");

        /**
         * Dynamic list 'Create' button (multiple contexts)
         */
        var $dynListsCreateButton = $("#dynamic_lists_create_new");

        /**
         * Dynamic list 'Create' button (single context)
         */
        var $dynListsCreateButtonSingleChoice = $("#dynamic_lists_create_new_single_choice");

        /**
         * Dynamic list 'Create' button container for multiple contexts
         */
        var $dynListsCreateButtonContainer = $("#dlm_create_button_multiple_choice_container");

        var $tableOfLists = $("#list_table");

        //////////////////////////
        // UI related functions //
        //////////////////////////

        /**
         * Shows a general message on the top screen
         * @param {String} msg    the message you want to display
         * @param {Boolean} isError    true for error (red block)/false for normal message(green block)
         */
        var showGeneralMessage = function(msg, isError){

            // Check whether to show an error type message or an information one
            var type = isError ? sakai.api.Util.notification.type.ERROR : sakai.api.Util.notification.type.INFORMATION;

            // Show the message to the user
            sakai.api.Util.notification.show("", msg, type);

        };

        /**
         * Check or uncheck all messages depending on the top checkbox.
         */
        var tickMessages = function(){
            $(".list_list_check_list").attr("checked", ($("#list_list_checkAll").is(":checked") ? "checked" : ''));
            updateEditCopyDeleteButtonsStatus();
        };

        /**
         * Removes all the messages out of the DOM.
         * It will also remove the preloader in the table.
         */
        var removeAllListsOutDOM = function(){
            $(".list_list").remove();
        };

        /**
         * sorts an array of dynamic lists on the date of last modification, this can be used with the JavaScript sort function.
         *
         * @param {Object} a  a dynamic list item
         * @param {Object} b  another dynamic list item
         *
         * @return {Number} Less than 0: Sort "a" to be a lower index than "b";
         *                   Zero: "a" and "b" should be considered equal, and no sorting performed;
         *                   Greater than 0: Sort "b" to be a lower index than "a".
         */
        var sortByDateFunction = function(a, b) {

            return a['_lastModified'] - b['_lastModified'];
        };

        /**
         * Renders loaded lists to the lists table
         */
        var displayLoadedLists = function() {

            //Sort by date
            var listsArray = [];
            for(var key in allLists){
                if(allLists.hasOwnProperty(key)) {
                    listsArray.push(allLists[key]);
                }
            }
            listsArray.sort(sortByDateFunction);

            var data = {
                links: listsArray, //allLists,
                sakai: sakai
            };

            // remove previous lists
            removeAllListsOutDOM();

            // Add them to the DOM
            $tableOfLists.children("tbody").append(sakai.api.Util.TemplateRenderer("#list_list_lists_template", data));

            // do checkboxes
            tickMessages();
        };

        var getNumberOfSelectedLists = function() {
            return $(".list_list_check_list:checked").length;
           };

        var updateEditCopyDeleteButtonsStatus = function() {
          var num = getNumberOfSelectedLists();
          if(num === 0) {
              $dynListsEditButton.attr('disabled', 'disabled');
              $dynListsCopyButton.attr('disabled', 'disabled');
              $dynListsDeleteButton.attr('disabled', 'disabled');
          } else if(num === 1){
              $dynListsEditButton.removeAttr('disabled');
              $dynListsCopyButton.removeAttr('disabled');
              $dynListsDeleteButton.removeAttr('disabled');
          } else if(num > 1){
              $dynListsEditButton.attr('disabled', 'disabled');
              $dynListsCopyButton.attr('disabled', 'disabled');
              $dynListsDeleteButton.removeAttr('disabled');
          }
        };

        var loadDynamicListsFromServer = function() {
            sakai.api.Server.loadJSON(dynamicListsBaseUrl, function(success, data) {
                if (success) {
                    allLists = data;
                    displayLoadedLists();
                } else {
                    allLists = [];
                    displayLoadedLists();
                }

                // TODO: HACK: To prevent flickering this widget was made invisible in HTML code, need to undo this
                $("div.dynamiclistmanager_widget", $rootElement).show();

            });
        };

        ////////////////////////////
        // Dynamic lists deletion //
        ////////////////////////////

         /**
         * This will do a DELETE request to the specified paths and delete each list.
         * @param {String[]} paths The array of dynamic lists that you want to delete.
         */
        var batchDeleteLists = function(paths) {
            var requests = [];
            $(paths).each(function(i,val) {
                var req = {
                    url: val,
                    method: "POST",
                    parameters: {
                        ":operation": "delete"
                    }
                };
                requests.push(req);
            });
            $.ajax({
                url: sakai.config.URL.BATCH,
                traditional: true,
                type: "POST",
                data: {
                    requests: $.toJSON(requests)
                },
                error: function() {
                   showGeneralMessage("Error deleting lists.", true);
                }
            });
        };

        /**
         * Removes lists with provided IDs from DOM and sends batch delete AJAX request.
         *
         *  @param listIds {Array} IDs of lists to delete
         */
        var deleteLists = function(listIds) {

            var paths = []; // paths to nodes to delete
            for (var i = 0, j = listIds.length; i < j; i++) {

                var currentId = listIds[i];

                if(allLists.hasOwnProperty(currentId)) {

                    $("#list_table_list_" + currentId).empty();
                    $("#list_table_list_" + currentId).remove();

                    // store path to delete
                    paths.push(dynamicListsBaseUrl + "/" + currentId);

                    // delete list from memory
                    delete allLists[currentId];
                } else {
                    alert("Error: list \"" + currentId + "\" not found");
                }
            }

            // batch delete nodes selected for deletion
            batchDeleteLists(paths);
        };

        ////////////////////
        // Click handlers //
        ////////////////////

        $dynListsCreateButton.click(function() {
            $.bbq.pushState({"new": true, "context": sakai.data.me.dynamiclistcontexts[0].name});
        });

        $dynListsCreateButtonSingleChoice.click(function() {
            $.bbq.pushState({"new": true, "context": sakai.data.me.dynamiclistcontexts[0].name});
        });

        $(".list_list_check_list").live("click", function(){
            updateEditCopyDeleteButtonsStatus();
        });

        // Button click events
        $dynListsDeleteButton.live("click", function(){
            var listId = [];
            $(".list_list_check_list:checked").each(function(){
                var id = $(this).val();
                listId.push(id);
            });

            $("#list_list_checkAll").removeAttr("checked");
            tickMessages();

            if (listId.length < 1) {
                showGeneralMessage($("#list_generalmessages_none_selected").text(), true);
            } else {
                deleteLists(listId);
                loadDynamicListsFromServer();
            }
        });

        $dynListsCopyButton.live("click", function(){
            var listIds = [];
            $(".list_list_check_list:checked").each(function(){
                var id = $(this).val();
                listIds.push(id);
            });

            if (listIds.length === 0) {
                return;
            }

            $("#list_list_checkAll").removeAttr("checked");
            tickMessages();

            // Display new list
            $.bbq.pushState({"cp": listIds[0]});

        });

        $dynListsEditButton.live("click", function(){

            var listIds = [];
            $(".list_list_check_list:checked", $rootElement).each(function(){
                var id = $(this).val();
                listIds.push(id);
            });

            if (listIds.length === 0) {
                return;
            }

            // Display edit list
            $.bbq.pushState({"edit": listIds[0]});
        });

        $(".editLink").live("click", function(evt){

            var id = evt.target.id;

            // Display edit list
            $.bbq.pushState({"edit": id});
        });

        // Check all messages
        $("#list_list_checkAll").change(function(){
            tickMessages();
        });

        ////////////////////////////////
        // Hashchange events handling //
        ////////////////////////////////

        /**
         * Sets current state of this component.
         * This function is called when hashchange event fires.
         */
         var setState = function(){

            var state = $.bbq.getState();

            if (!(state.hasOwnProperty("l") && state.l === "dynlists")) {
                return;
            }

            if (state.hasOwnProperty("new") || state.hasOwnProperty("edit") || state.hasOwnProperty("cp")) {
                 $rootElement.hide();
            } else {
                loadDynamicListsFromServer();
                $rootElement.show();
            }

        };

         $(window).bind('hashchange', function() {
            setState();
        });

        /////////////////////////////
        // Initialization function //
        /////////////////////////////

        /**
         * Initialization function that is run when the widget is loaded. Determines
         * which mode the widget is in (settings or main), loads the necessary data
         * and shows the correct view.
         */
        var doInit = function() {
            var security = sakai.api.Security;

            // if the user is not a member of the advisers group then bail
            if (!myb.api.security.isUserAnAdviser()) {
                security.send403();
                return;
            }

            dynamicListsBaseUrl = "/~" + sakai.data.me.user.userid + "/private/dynamic_lists";

            setState();
            loadDynamicListsFromServer();

            var contexts = sakai.data.me.dynamiclistcontexts;

            if(contexts.length>1) {
                // Display multiple choice drop-down menu
                $dynListsCreateButtonContainer.hover(function(){
                    var $popup = $("#dlm_create_button_dropdown");
                    $popup.show();
                }, function(){
                    var $popup = $("#dlm_create_button_dropdown");
                    $popup.hide();
                });

                var $list = $("#dlm_create_button_multiple_choice_container #dlm_create_button_dropdown ul", $rootElement);
                for(var key in contexts) {
                    var c = contexts[key];
                    $list.append('
'+c.name+'
');
                }

                $("a.dynlist_context", $list).click(function() {
                    var $a=$(this);
                    $.bbq.pushState({"new": true, "context": $a.text()});
                });
                $dynListsCreateButtonContainer.show();
            } else {
                $dynListsCreateButtonSingleChoice.show();
            }
        };

        doInit();

    };

    sakai.api.Widgets.widgetLoader.informOnLoad("dynamiclistmanager");

});<commit_msg>MYB-769 Fixed a deletion bug where dynamic lists were reloaded before JSON deletion request returned success<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/* global $, Config, jQuery, sakai, sdata */

require(["jquery","sakai/sakai.api.core", "myb/myb.api.core", "/dev/javascript/myb/myb.securepage.js"], function($, sakai, myb) {
    /**
     * @name sakai_global.dynamiclistmanager
     *
     * @class dynamiclistmanager
     *
     * @description
     * Dynamic list manager widget
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.dynamiclistmanager = function(tuid, showSettings) {

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        /**
         * Hashtable of all the lists
         */
        var allLists = {};

        /**
         * Dynamic lists base URL (parent node in Sparse), delete this node to remove all dynamic lists for current user
         */
        var dynamicListsBaseUrl;

        //////////////////////
        // jQuery selectors //
        //////////////////////

        /**
         * Widget's root element
         */
        var $rootElement = $("#" + tuid);

        /**
         * Dynamic list 'Edit' button
         */
        var $dynListsEditButton = $("#dyn_lists_edit_button");

        /**
         * Dynamic list 'Copy' button
         */
        var $dynListsCopyButton = $("#dyn_lists_copy_button");

        /**
         * Dynamic list 'delete' button
         */
        var $dynListsDeleteButton = $("#dyn_lists_delete_button");

        /**
         * Dynamic list 'Create' button (multiple contexts)
         */
        var $dynListsCreateButton = $("#dynamic_lists_create_new");

        /**
         * Dynamic list 'Create' button (single context)
         */
        var $dynListsCreateButtonSingleChoice = $("#dynamic_lists_create_new_single_choice");

        /**
         * Dynamic list 'Create' button container for multiple contexts
         */
        var $dynListsCreateButtonContainer = $("#dlm_create_button_multiple_choice_container");

        var $tableOfLists = $("#list_table");

        //////////////////////////
        // UI related functions //
        //////////////////////////

        /**
         * Shows a general message on the top screen
         * @param {String} msg    the message you want to display
         * @param {Boolean} isError    true for error (red block)/false for normal message(green block)
         */
        var showGeneralMessage = function(msg, isError){

            // Check whether to show an error type message or an information one
            var type = isError ? sakai.api.Util.notification.type.ERROR : sakai.api.Util.notification.type.INFORMATION;

            // Show the message to the user
            sakai.api.Util.notification.show("", msg, type);

        };

        /**
         * Check or uncheck all messages depending on the top checkbox.
         */
        var tickMessages = function(){
            $(".list_list_check_list").attr("checked", ($("#list_list_checkAll").is(":checked") ? "checked" : ''));
            updateEditCopyDeleteButtonsStatus();
        };

        /**
         * Removes all the messages out of the DOM.
         * It will also remove the preloader in the table.
         */
        var removeAllListsOutDOM = function(){
            $(".list_list").remove();
        };

        /**
         * sorts an array of dynamic lists on the date of last modification, this can be used with the JavaScript sort function.
         *
         * @param {Object} a  a dynamic list item
         * @param {Object} b  another dynamic list item
         *
         * @return {Number} Less than 0: Sort "a" to be a lower index than "b";
         *                   Zero: "a" and "b" should be considered equal, and no sorting performed;
         *                   Greater than 0: Sort "b" to be a lower index than "a".
         */
        var sortByDateFunction = function(a, b) {

            return a['_lastModified'] - b['_lastModified'];
        };

        /**
         * Renders loaded lists to the lists table
         */
        var displayLoadedLists = function() {

            //Sort by date
            var listsArray = [];
            for(var key in allLists){
                if(allLists.hasOwnProperty(key)) {
                    listsArray.push(allLists[key]);
                }
            }
            listsArray.sort(sortByDateFunction);

            var data = {
                links: listsArray, //allLists,
                sakai: sakai
            };

            // remove previous lists
            removeAllListsOutDOM();

            // Add them to the DOM
            $tableOfLists.children("tbody").append(sakai.api.Util.TemplateRenderer("#list_list_lists_template", data));

            // do checkboxes
            tickMessages();
        };

        var getNumberOfSelectedLists = function() {
            return $(".list_list_check_list:checked").length;
           };

        var updateEditCopyDeleteButtonsStatus = function() {
          var num = getNumberOfSelectedLists();
          if(num === 0) {
              $dynListsEditButton.attr('disabled', 'disabled');
              $dynListsCopyButton.attr('disabled', 'disabled');
              $dynListsDeleteButton.attr('disabled', 'disabled');
          } else if(num === 1){
              $dynListsEditButton.removeAttr('disabled');
              $dynListsCopyButton.removeAttr('disabled');
              $dynListsDeleteButton.removeAttr('disabled');
          } else if(num > 1){
              $dynListsEditButton.attr('disabled', 'disabled');
              $dynListsCopyButton.attr('disabled', 'disabled');
              $dynListsDeleteButton.removeAttr('disabled');
          }
        };

        var loadDynamicListsFromServer = function() {
            sakai.api.Server.loadJSON(dynamicListsBaseUrl, function(success, data) {
                if (success) {
                    allLists = data;
                    displayLoadedLists();
                } else {
                    allLists = [];
                    displayLoadedLists();
                }

                // TODO: HACK: To prevent flickering this widget was made invisible in HTML code, need to undo this
                $("div.dynamiclistmanager_widget", $rootElement).show();

            });
        };

        ////////////////////////////
        // Dynamic lists deletion //
        ////////////////////////////

         /**
         * This will do a DELETE request to the specified paths and delete each list.
         * @param {String[]} paths The array of dynamic lists that you want to delete.
         * @param successCallback Function to call if deletion was successful.
         */
        var batchDeleteLists = function(paths, successCallback) {
            var requests = [];
            $(paths).each(function(i,val) {
                var req = {
                    url: val,
                    method: "POST",
                    parameters: {
                        ":operation": "delete"
                    }
                };
                requests.push(req);
            });
            $.ajax({
                url: sakai.config.URL.BATCH,
                traditional: true,
                type: "POST",
                data: {
                    requests: $.toJSON(requests)
                },
                success: function(){
                    if ($.isFunction(successCallback)){
                        successCallback();
                    }
                },
                error: function() {
                   showGeneralMessage("Error deleting lists.", true);
                }
            });
        };

        /**
         * Removes lists with provided IDs from DOM and sends batch delete AJAX request.
         *
         *  @param listIds {Array} IDs of lists to delete.
         *  @param successCallback Function to call if deletion was successful.
         */
        var deleteLists = function(listIds, successCallback) {

            var paths = []; // paths to nodes to delete
            for (var i = 0, j = listIds.length; i < j; i++) {

                var currentId = listIds[i];

                if(allLists.hasOwnProperty(currentId)) {

                    $("#list_table_list_" + currentId).empty();
                    $("#list_table_list_" + currentId).remove();

                    // store path to delete
                    paths.push(dynamicListsBaseUrl + "/" + currentId);

                    // delete list from memory
                    delete allLists[currentId];
                } else {
                    alert("Error: list \"" + currentId + "\" not found");
                }
            }

            // batch delete nodes selected for deletion
            batchDeleteLists(paths, successCallback);
        };

        ////////////////////
        // Click handlers //
        ////////////////////

        $dynListsCreateButton.click(function() {
            $.bbq.pushState({"new": true, "context": sakai.data.me.dynamiclistcontexts[0].name});
        });

        $dynListsCreateButtonSingleChoice.click(function() {
            $.bbq.pushState({"new": true, "context": sakai.data.me.dynamiclistcontexts[0].name});
        });

        $(".list_list_check_list").live("click", function(){
            updateEditCopyDeleteButtonsStatus();
        });

        // Button click events
        $dynListsDeleteButton.live("click", function(){
            var listId = [];
            $(".list_list_check_list:checked").each(function(){
                var id = $(this).val();
                listId.push(id);
            });

            $("#list_list_checkAll").removeAttr("checked");
            tickMessages();

            if (listId.length < 1) {
                showGeneralMessage($("#list_generalmessages_none_selected").text(), true);
            } else {
                deleteLists(listId, loadDynamicListsFromServer);
            }
        });

        $dynListsCopyButton.live("click", function(){
            var listIds = [];
            $(".list_list_check_list:checked").each(function(){
                var id = $(this).val();
                listIds.push(id);
            });

            if (listIds.length === 0) {
                return;
            }

            $("#list_list_checkAll").removeAttr("checked");
            tickMessages();

            // Display new list
            $.bbq.pushState({"cp": listIds[0]});

        });

        $dynListsEditButton.live("click", function(){

            var listIds = [];
            $(".list_list_check_list:checked", $rootElement).each(function(){
                var id = $(this).val();
                listIds.push(id);
            });

            if (listIds.length === 0) {
                return;
            }

            // Display edit list
            $.bbq.pushState({"edit": listIds[0]});
        });

        $(".editLink").live("click", function(evt){

            var id = evt.target.id;

            // Display edit list
            $.bbq.pushState({"edit": id});
        });

        // Check all messages
        $("#list_list_checkAll").change(function(){
            tickMessages();
        });

        ////////////////////////////////
        // Hashchange events handling //
        ////////////////////////////////

        /**
         * Sets current state of this component.
         * This function is called when hashchange event fires.
         */
         var setState = function(){

            var state = $.bbq.getState();

            if (!(state.hasOwnProperty("l") && state.l === "dynlists")) {
                return;
            }

            if (state.hasOwnProperty("new") || state.hasOwnProperty("edit") || state.hasOwnProperty("cp")) {
                 $rootElement.hide();
            } else {
                loadDynamicListsFromServer();
                $rootElement.show();
            }

        };

         $(window).bind('hashchange', function() {
            setState();
        });

        /////////////////////////////
        // Initialization function //
        /////////////////////////////

        /**
         * Initialization function that is run when the widget is loaded. Determines
         * which mode the widget is in (settings or main), loads the necessary data
         * and shows the correct view.
         */
        var doInit = function() {
            var security = sakai.api.Security;

            // if the user is not a member of the advisers group then bail
            if (!myb.api.security.isUserAnAdviser()) {
                security.send403();
                return;
            }

            dynamicListsBaseUrl = "/~" + sakai.data.me.user.userid + "/private/dynamic_lists";

            setState();
            loadDynamicListsFromServer();

            var contexts = sakai.data.me.dynamiclistcontexts;

            if(contexts.length>1) {
                // Display multiple choice drop-down menu
                $dynListsCreateButtonContainer.hover(function(){
                    var $popup = $("#dlm_create_button_dropdown");
                    $popup.show();
                }, function(){
                    var $popup = $("#dlm_create_button_dropdown");
                    $popup.hide();
                });

                var $list = $("#dlm_create_button_multiple_choice_container #dlm_create_button_dropdown ul", $rootElement);
                for(var key in contexts) {
                    var c = contexts[key];
                    $list.append('
'+c.name+'
');
                }

                $("a.dynlist_context", $list).click(function() {
                    var $a=$(this);
                    $.bbq.pushState({"new": true, "context": $a.text()});
                });
                $dynListsCreateButtonContainer.show();
            } else {
                $dynListsCreateButtonSingleChoice.show();
            }
        };

        doInit();

    };

    sakai.api.Widgets.widgetLoader.informOnLoad("dynamiclistmanager");

});<|endoftext|>


<commit_before>import React from 'react';
import logger from 'electron-timber';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ipcRenderer } from 'electron';
import * as PlayerActions from '../../actions/player';
import * as QueueActions from '../../actions/queue';
import * as SettingsActions from '../../actions/settings';
import * as PlaylistActions from '../../actions/playlists';
import * as EqualizerActions from '../../actions/equalizer';
import * as DownloadsActions from '../../actions/downloads';
import * as LocalFileActions from '../../actions/local';

import {
  onNext,
  onPrevious,
  onPause,
  onPlayPause,
  onStop,
  onPlay,
  onSongChange,
  onSettings,
  onVolume,
  onSeek,
  sendPlayingStatus,
  sendQueueItems,
  onMute,
  onEmptyQueue,
  onCreatePlaylist,
  onRefreshPlaylists,
  onUpdateEqualizer,
  onSetEqualizer,
  onLocalFilesProgress,
  onLocalFiles,
  onLocalFilesError,
  onSelectTrack,
  onActivatePlaylist
} from '../../mpris';

class IpcContainer extends React.Component {
  componentDidMount() {
    ipcRenderer.send('started');
    ipcRenderer.send('refresh-localfolders');
    ipcRenderer.on('next', event => onNext(event, this.props.actions));
    ipcRenderer.on('previous', event => onPrevious(event, this.props.actions));
    ipcRenderer.on('pause', event => onPause(event, this.props.actions));
    ipcRenderer.on('playpause', event => onPlayPause(event, this.props.actions, this.props.player));
    ipcRenderer.on('stop', event => onStop(event, this.props.actions));
    ipcRenderer.on('play', event => onPlay(event, this.props.actions));
    ipcRenderer.on('settings', (event, data) => onSettings(event, data, this.props.actions));
    ipcRenderer.on('mute', event => onMute(event, this.props.actions, this.props.player));
    ipcRenderer.on('volume', (event, data) => onVolume(event, data, this.props.actions));
    ipcRenderer.on('seek', (event, data) => onSeek(event, data, this.props.actions));
    ipcRenderer.on('playing-status', event => sendPlayingStatus(event, this.props.player, this.props.queue, this.props.settings));
    ipcRenderer.on('empty-queue', event => onEmptyQueue(event, this.props.actions));
    ipcRenderer.on('queue', () => sendQueueItems(this.props.queue.queueItems));
    ipcRenderer.on('select-track', (event, index) => onSelectTrack(index, this.props.actions));
    ipcRenderer.on('create-playlist', (event, name) => onCreatePlaylist(event, { name, tracks: this.props.queue.queueItems }, this.props.actions));
    ipcRenderer.on('refresh-playlists', (event) => onRefreshPlaylists(event, this.props.actions));
    ipcRenderer.on('activate-playlist', (event, name) => onActivatePlaylist(this.props.playlists, name, this.props.streamProviders, this.props.actions));
    ipcRenderer.on('update-equalizer', (event, data) =>  onUpdateEqualizer(event, this.props.actions, data));
    ipcRenderer.on('set-equalizer', (event, data) => onSetEqualizer(event, this.props.actions, data));
    ipcRenderer.on('local-files-progress', (event, data) => onLocalFilesProgress(event, this.props.actions, data));
    ipcRenderer.on('local-files', (event, data) => onLocalFiles(event, this.props.actions, data));
    ipcRenderer.on('local-files-error', (event, err) => onLocalFilesError(event, this.props.actions, err));
    ipcRenderer.on('play-startup-track', (event, track) => {
      this.props.actions.playTrack(this.props.streamProviders, track);
    });

    ipcRenderer.on('download-started', (event, data) => {
      this.props.actions.onDownloadStarted(data);
    });
    ipcRenderer.on('download-progress', (event, data) => {
      this.props.actions.onDownloadProgress(data.uuid, data.progress);
    });
    ipcRenderer.on('download-finished', (event, data) => {
      this.props.actions.onDownloadFinished(data);
    });
    ipcRenderer.on('download-error', (event, data) => {
      logger.error(data);
    });
  }

  componentDidUpdate(prevProps) {
    const { queue } = this.props;

    if (queue.queueItems[queue.currentSong] !== prevProps.queue.queueItems[prevProps.queue.currentSong]) {
      const currentSong = queue.queueItems[queue.currentSong];
      onSongChange(currentSong);
    }
  }

  render() {
    return null;
  }
}

function mapStateToProps(state) {
  return {
    player: state.player,
    queue: state.queue,
    settings: state.settings,
    playlists: state.playlists.playlists,
    streamProviders: state.plugin.plugins.streamProviders
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(
      Object.assign(
        {},
        PlayerActions,
        QueueActions,
        SettingsActions,
        PlaylistActions,
        EqualizerActions,
        DownloadsActions,
        LocalFileActions
      ),
      dispatch
    )
  };
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IpcContainer));
<commit_msg>fix(app): use explicitly Local plugin for startup track<commit_after>import React from 'react';
import logger from 'electron-timber';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ipcRenderer } from 'electron';
import * as PlayerActions from '../../actions/player';
import * as QueueActions from '../../actions/queue';
import * as SettingsActions from '../../actions/settings';
import * as PlaylistActions from '../../actions/playlists';
import * as EqualizerActions from '../../actions/equalizer';
import * as DownloadsActions from '../../actions/downloads';
import * as LocalFileActions from '../../actions/local';

import {
  onNext,
  onPrevious,
  onPause,
  onPlayPause,
  onStop,
  onPlay,
  onSongChange,
  onSettings,
  onVolume,
  onSeek,
  sendPlayingStatus,
  sendQueueItems,
  onMute,
  onEmptyQueue,
  onCreatePlaylist,
  onRefreshPlaylists,
  onUpdateEqualizer,
  onSetEqualizer,
  onLocalFilesProgress,
  onLocalFiles,
  onLocalFilesError,
  onSelectTrack,
  onActivatePlaylist
} from '../../mpris';

class IpcContainer extends React.Component {
  componentDidMount() {
    ipcRenderer.send('started');
    ipcRenderer.send('refresh-localfolders');
    ipcRenderer.on('next', event => onNext(event, this.props.actions));
    ipcRenderer.on('previous', event => onPrevious(event, this.props.actions));
    ipcRenderer.on('pause', event => onPause(event, this.props.actions));
    ipcRenderer.on('playpause', event => onPlayPause(event, this.props.actions, this.props.player));
    ipcRenderer.on('stop', event => onStop(event, this.props.actions));
    ipcRenderer.on('play', event => onPlay(event, this.props.actions));
    ipcRenderer.on('settings', (event, data) => onSettings(event, data, this.props.actions));
    ipcRenderer.on('mute', event => onMute(event, this.props.actions, this.props.player));
    ipcRenderer.on('volume', (event, data) => onVolume(event, data, this.props.actions));
    ipcRenderer.on('seek', (event, data) => onSeek(event, data, this.props.actions));
    ipcRenderer.on('playing-status', event => sendPlayingStatus(event, this.props.player, this.props.queue, this.props.settings));
    ipcRenderer.on('empty-queue', event => onEmptyQueue(event, this.props.actions));
    ipcRenderer.on('queue', () => sendQueueItems(this.props.queue.queueItems));
    ipcRenderer.on('select-track', (event, index) => onSelectTrack(index, this.props.actions));
    ipcRenderer.on('create-playlist', (event, name) => onCreatePlaylist(event, { name, tracks: this.props.queue.queueItems }, this.props.actions));
    ipcRenderer.on('refresh-playlists', (event) => onRefreshPlaylists(event, this.props.actions));
    ipcRenderer.on('activate-playlist', (event, name) => onActivatePlaylist(this.props.playlists, name, this.props.streamProviders, this.props.actions));
    ipcRenderer.on('update-equalizer', (event, data) =>  onUpdateEqualizer(event, this.props.actions, data));
    ipcRenderer.on('set-equalizer', (event, data) => onSetEqualizer(event, this.props.actions, data));
    ipcRenderer.on('local-files-progress', (event, data) => onLocalFilesProgress(event, this.props.actions, data));
    ipcRenderer.on('local-files', (event, data) => onLocalFiles(event, this.props.actions, data));
    ipcRenderer.on('local-files-error', (event, err) => onLocalFilesError(event, this.props.actions, err));
    ipcRenderer.on('play-startup-track', (event, track) => {
      this.props.actions.playTrack(
        this.props.streamProviders.filter(({ sourceName }) => sourceName === 'Local'),
        track
      );
    });

    ipcRenderer.on('download-started', (event, data) => {
      this.props.actions.onDownloadStarted(data);
    });
    ipcRenderer.on('download-progress', (event, data) => {
      this.props.actions.onDownloadProgress(data.uuid, data.progress);
    });
    ipcRenderer.on('download-finished', (event, data) => {
      this.props.actions.onDownloadFinished(data);
    });
    ipcRenderer.on('download-error', (event, data) => {
      logger.error(data);
    });
  }

  componentDidUpdate(prevProps) {
    const { queue } = this.props;

    if (queue.queueItems[queue.currentSong] !== prevProps.queue.queueItems[prevProps.queue.currentSong]) {
      const currentSong = queue.queueItems[queue.currentSong];
      onSongChange(currentSong);
    }
  }

  render() {
    return null;
  }
}

function mapStateToProps(state) {
  return {
    player: state.player,
    queue: state.queue,
    settings: state.settings,
    playlists: state.playlists.playlists,
    streamProviders: state.plugin.plugins.streamProviders
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(
      Object.assign(
        {},
        PlayerActions,
        QueueActions,
        SettingsActions,
        PlaylistActions,
        EqualizerActions,
        DownloadsActions,
        LocalFileActions
      ),
      dispatch
    )
  };
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IpcContainer));
<|endoftext|>


<commit_before>const picoSpeaker = require("pico-speaker");
const script = require("./scripts/scripts");

const sky = {
  start: () => {
    sky.scriptedPhrase("welcome");
  },
  say: (text) => picoSpeaker.speak(text),
  scriptedPhrase: (key) => sky.say(script(key))
};

module.exports = sky;
<commit_msg>adding silent mode for ski (is not working perfectly yet)<commit_after>const picoSpeaker = require("pico-speaker");
const script = require("./scripts/scripts");
const homepath = require("../homepath");
const toast = require("../toast");

const fs = require("fs");

const isSilentMode = fs.existsSync(`${homepath()}/.sky-silent-mode`);

const sky = {
  start: () => {
    sky.scriptedPhrase("welcome");
  },
  say: (text) => isSilentMode ? Promise.resolve(toast.normal("Sky", text)) : picoSpeaker.speak(text),
  scriptedPhrase: (key) => sky.say(script(key))
};

module.exports = sky;
<|endoftext|>


<commit_before>export const customStyles = error => {
    console.log("3px solid " + (error ? "#aa354c" : "#58585B"));
    return {
        option: (styles, state) => ({
            ...styles,
            color: "black",
            backgroundColor: state.isSelected ? "#ddd" : state.isFocused ? "#ddd" : "white",
            ":active": {
                ...styles[":active"],
                backgroundColor: state.isSelected ? "#ddd" : "#ddd",
            },
            padding: 20,
        }),
        container: (styles, state) => ({
            ...styles,
            zIndex: state.isFocused ? 1 : 0,
            border: "3px solid " + (error ? "#aa354c" : "#58585B"),
            borderRadius: "4px",
            boxShadow: state.isFocused ? "0 0 0 3px #ffbf47" : "none",
            backgroundColor: error ? "#f7e7ea" : "#FFFFFF",
        }),
        control: styles => ({
            ...styles,
            border: "none",
            backgroundColor: "transparent",
            boxShadow: "none",
        }),
        multiValueRemove: styles => ({
            ...styles,
            ":hover": {
                backgroundColor: "#ddd",
                color: "#333333",
            },
        }),
    };
};
<commit_msg>removes console log<commit_after>export const customStyles = error => {
    return {
        option: (styles, state) => ({
            ...styles,
            color: "black",
            backgroundColor: state.isSelected ? "#ddd" : state.isFocused ? "#ddd" : "white",
            ":active": {
                ...styles[":active"],
                backgroundColor: state.isSelected ? "#ddd" : "#ddd",
            },
            padding: 20,
        }),
        container: (styles, state) => ({
            ...styles,
            zIndex: state.isFocused ? 1 : 0,
            border: "3px solid " + (error ? "#aa354c" : "#58585B"),
            borderRadius: "4px",
            boxShadow: state.isFocused ? "0 0 0 3px #ffbf47" : "none",
            backgroundColor: error ? "#f7e7ea" : "#FFFFFF",
        }),
        control: styles => ({
            ...styles,
            border: "none",
            backgroundColor: "transparent",
            boxShadow: "none",
        }),
        multiValueRemove: styles => ({
            ...styles,
            ":hover": {
                backgroundColor: "#ddd",
                color: "#333333",
            },
        }),
    };
};
<|endoftext|>


<commit_before>$(function () {
    var energyCanvas = $('<canvas/>',{'id':'energySmoothieChart'})
    .width(280)
    .height(76)
	$("body").append(energyCanvas);

    var metalCanvas = $('<canvas/>',{'id':'metalSmoothieChart'})
    .width(280)
    .height(76);
    $("body").append(metalCanvas);

   function rangeFunction(range) {
	  // TODO implement your calculation using range.min and range.max
	  var min = 0;
	  var max = range.max + range.max/20;
	  return {min: min, max: max};
	}
    //{millisPerPixel:100,grid:{fillStyle:'rgba(0,0,0,0.17)',sharpLines:true,millisPerLine:10000,verticalSections:20},labels:{disabled:true}}
    var energyChart = new SmoothieChart({millisPerPixel:350,
    									grid:{millisPerLine:10000,
    										fillStyle:'rgba(0,0,0,0.75)',
    										strokeStyle:'rgba(255,255,255,0.25)',
    										interpolation:'bezier',
    										verticalSections:10,
    										sharpLines:true},
    									labels:{disabled:true},
    									yRangeFunction:rangeFunction});

	var metalChart = new SmoothieChart({millisPerPixel:350,
    									grid:{millisPerLine:10000,
    										fillStyle:'rgba(0,0,0,0.75)',
    										strokeStyle:'rgba(255,255,255,0.25)',
    										interpolation:'bezier',
    										verticalSections:10,
    										sharpLines:true},
    									labels:{disabled:true},
    									yRangeFunction:rangeFunction});

	var energyUsage = new TimeSeries();
	var energyIncome = new TimeSeries();
	var metalUsage = new TimeSeries();
	var metalIncome = new TimeSeries();
	setInterval(function() {
		  energyIncome.append(new Date().getTime(), model.energyGain());
		  energyUsage.append(new Date().getTime(), model.energyLoss());
		  metalIncome.append(new Date().getTime(), model.metalGain());
		  metalUsage.append(new Date().getTime(), model.metalLoss());
	});
	energyChart.addTimeSeries(energyUsage,{strokeStyle:'#850505',lineWidth:0,fillStyle:'rgba(254,1,39,0.50)'});
	energyChart.addTimeSeries(energyIncome,{strokeStyle:'#ddd01f',lineWidth:2});

	metalChart.addTimeSeries(metalUsage,{strokeStyle:'#850505',lineWidth:0,fillStyle:'rgba(254,1,39,0.50)'});
	metalChart.addTimeSeries(metalIncome,{strokeStyle:'#01b3f4',lineWidth:2});

	energyChart.streamTo(document.getElementById("energySmoothieChart"),5);
	metalChart.streamTo(document.getElementById("metalSmoothieChart"),5);
});<commit_msg>Added resource gain/loss handlers to Live Game model as they are not in there by default<commit_after>$(function () {
    var energyCanvas = $('<canvas/>',{'id':'energySmoothieChart'})
    .width(280)
    .height(76)
	$("body").append(energyCanvas);

    var metalCanvas = $('<canvas/>',{'id':'metalSmoothieChart'})
    .width(280)
    .height(76);
    $("body").append(metalCanvas);

    handlers.army = function (payload) {
        model.currentEnergy(payload.energy.current);
        model.maxEnergy(payload.energy.storage);
        model.energyGain(payload.energy.production);
        model.energyLoss(payload.energy.demand);

        model.currentMetal(payload.metal.current);
        model.maxMetal(payload.metal.storage);
        model.metalGain(payload.metal.production);
        model.metalLoss(payload.metal.demand);
    }

    model.energyGain = ko.observable(1.0);
    model.energyLoss = ko.observable(1.0);
    model.metalGain = ko.observable(1.0);
    model.metalLoss = ko.observable(1.0);

   function rangeFunction(range) {
	  // TODO implement your calculation using range.min and range.max
	  var min = 0;
	  var max = range.max + range.max/20;
	  return {min: min, max: max};
	}
    //{millisPerPixel:100,grid:{fillStyle:'rgba(0,0,0,0.17)',sharpLines:true,millisPerLine:10000,verticalSections:20},labels:{disabled:true}}
    var energyChart = new SmoothieChart({millisPerPixel:350,
    									grid:{millisPerLine:10000,
    										fillStyle:'rgba(0,0,0,0.75)',
    										strokeStyle:'rgba(255,255,255,0.25)',
    										interpolation:'bezier',
    										verticalSections:10,
    										sharpLines:true},
    									labels:{disabled:true},
    									yRangeFunction:rangeFunction});

	var metalChart = new SmoothieChart({millisPerPixel:350,
    									grid:{millisPerLine:10000,
    										fillStyle:'rgba(0,0,0,0.75)',
    										strokeStyle:'rgba(255,255,255,0.25)',
    										interpolation:'bezier',
    										verticalSections:10,
    										sharpLines:true},
    									labels:{disabled:true},
    									yRangeFunction:rangeFunction});

	var energyUsage = new TimeSeries();
	var energyIncome = new TimeSeries();
	var metalUsage = new TimeSeries();
	var metalIncome = new TimeSeries();
	setInterval(function() {
		  energyIncome.append(new Date().getTime(), model.energyGain());
		  energyUsage.append(new Date().getTime(), model.energyLoss());
		  metalIncome.append(new Date().getTime(), model.metalGain());
		  metalUsage.append(new Date().getTime(), model.metalLoss());
	});
	energyChart.addTimeSeries(energyUsage,{strokeStyle:'#850505',lineWidth:0,fillStyle:'rgba(254,1,39,0.50)'});
	energyChart.addTimeSeries(energyIncome,{strokeStyle:'#ddd01f',lineWidth:2});

	metalChart.addTimeSeries(metalUsage,{strokeStyle:'#850505',lineWidth:0,fillStyle:'rgba(254,1,39,0.50)'});
	metalChart.addTimeSeries(metalIncome,{strokeStyle:'#01b3f4',lineWidth:2});

	energyChart.streamTo(document.getElementById("energySmoothieChart"),5);
	metalChart.streamTo(document.getElementById("metalSmoothieChart"),5);
});<|endoftext|>


<commit_before>// Rdio Plug-in
/**
  * Rdio popcorn plug-in
  * Appends Rdio album track listings to an element on the page.
  * Can also append a user's playlist to an element on the page.
  * Option paramter can be in two forms:
  * Options parameter will take a start, end, target, artist, album, and type or
  * Options parameter will take a start, end, target, person, id, playlist, and type
  * Start is the time that you want this plug-in to execute
  * End is the time that you want this plug-in to stop executing
  * Target is the id of the document element that the images are appended to
  * Artist is the name of who's album image will display
  * Album is the album that will display of the specified Artist
  * Person is the Rdio member who's playlist will display
  * ID is the playlist's unqiue Rdio playlist identifier
  * Playlist is the name of the playlist
  * Type specifies if the element is an album or playlist
  *

  *
  * @param {Object} options
  *
  * Example 1:
  var p = Popcorn( "#video" )
  .rdio({
    start: 2,
    end: 10,
    target: "rdiodiv",
    artist: "Jamiroquai",
    album: "Synkronized",
    type: "album"
  })
  *
  * Example 2:
  var p = Popcorn( "#video" )
  .rdio({
    start: 10,
    end: 20,
    target: "rdiodiv",
    person: "diggywiggy",
    id: 413517,
    playlist: "sunday",
    type: "playlist"
  })
**/

(function( Popcorn ) {
  var _album = {},
  _container = {},
  _target = {},
  _rdioURL = "http://www.rdio.com/api/oembed/?format=json&url=http://www.rdio.com/%23";

  Popcorn.plugin( "rdio", ( function( options ) {
    var _loadResults = function( data, options ) {
      var title = data.title,
      html = data.html;
      if( data && title && html ) {
        _album[ options.containerid ].htmlString = "
" + html + "
";
      } else {
        if( Popcorn.plugin.debug ) {
          throw new Error( "Did not receive data from server." );
        }
      }
    },

    // Handle AJAX Request
    _getResults = function( options ) {
      var urlBuilder = {
        playlist : ( function() {
          return _rdioURL + "/people/" + ( options.person ) + "/playlists/" + options.id + "/" + options.playlist + "/&callback=_loadResults";
        }()),
        album : ( function() {
          return _rdioURL + "/artist/" + ( options.artist ) + "/album/" + options.album + "/&callback=_loadResults";
        }())
      },
      url = urlBuilder[ options.type ];
      Popcorn.getJSONP( url, function( data ) {
        _loadResults( data, options );
      }, false );
    };

    return {
      _setup: function( options ) {
        options.containerid = Popcorn.guid();
        var key = options.containerid,
        container = _container[ key ] = document.createElement( "div" ),
        target = _target[ key ] = document.getElementById( options.target );
        if( !target && Popcorn.plugin.debug ) {
          throw new Error( "Target container could not be found." );
        }
        container.style.display = "none";
        container.innerHTML = "";
        target.appendChild( container );
        _album[ key ] = {
          htmlString: ( options.playlist || "Unknown Source" ) || ( options.album || "Unknown Source" )
        };
        _getResults( options );
      },
      start: function( event, options ) {
        var key = options.containerid,
        container = _container[ key ];
        container.innerHTML = _album[ key ].htmlString;
        container.style.display = "inline";
      },
      end: function( event, options ) {
        container = _container[ options.containerid ];
        container.style.display = "none";
        container.innerHTML = "";
      },
      _teardown: function( options ) {
        var key = options.containerid,
        target = _target[ key ];
        if ( _album[ key ] ) {
          delete _album [ key ];
        }
        target && target.removeChild( _container[ key ] );
      }
    };
  })(),
  {
    manifest: {
      about: {
        name: "Popcorn Rdio Plugin",
        version: "0.1",
        author: "Denise Rigato"
      },
      options: {
        start: {
          elem: "input",
          type: "text",
          label: "In"
        },
        end: {
          elem: "input",
          type: "text",
          label: "Out"
        },
        target: "rdio",
        artist: {
          elem: "input",
          type: "text",
          label: "Artist"
        },
        album: {
          elem: "input",
          type: "text",
          label: "Album"
        },
        person: {
          elem: "input",
          type: "text",
          label: "Person"
        },
        id: {
          elem: "input",
          type: "text",
          label: "Id"
        },
        playlist: {
          elem: "input",
          type: "text",
          label: "Playlist"
        }
      }
    }
  });
}( Popcorn ));<commit_msg>changed array key from title to guid<commit_after>// Rdio Plug-in
/**
  * Rdio popcorn plug-in
  * Appends Rdio album track listings to an element on the page.
  * Can also append a user's playlist to an element on the page.
  * Option paramter can be in two forms:
  * Options parameter will take a start, end, target, artist, album, and type or
  * Options parameter will take a start, end, target, person, id, playlist, and type
  * Start is the time that you want this plug-in to execute
  * End is the time that you want this plug-in to stop executing
  * Target is the id of the document element that the images are appended to
  * Artist is the name of who's album image will display
  * Album is the album that will display of the specified Artist
  * Person is the Rdio member who's playlist will display
  * ID is the playlist's unqiue Rdio playlist identifier
  * Playlist is the name of the playlist
  * Type specifies if the element is an album or playlist
  *

  *
  * @param {Object} options
  *
  * Example 1:
  var p = Popcorn( "#video" )
  .rdio({
    start: 2,
    end: 10,
    target: "rdiodiv",
    artist: "Jamiroquai",
    album: "Synkronized",
    type: "album"
  })
  *
  * Example 2:
  var p = Popcorn( "#video" )
  .rdio({
    start: 10,
    end: 20,
    target: "rdiodiv",
    person: "diggywiggy",
    id: 413517,
    playlist: "sunday",
    type: "playlist"
  })
**/

(function( Popcorn ) {
  var _album = {},
  _container = {},
  _target = {},
  _rdioURL = "http://www.rdio.com/api/oembed/?format=json&url=http://www.rdio.com/%23";

  Popcorn.plugin( "rdio", ( function( options ) {
    var _loadResults = function( data, options ) {
      var title = data.title,
      html = data.html;
      if( data && title && html ) {
        _album[ options.containerid ].htmlString = "
" + html + "
";
      } else {
        if( Popcorn.plugin.debug ) {
          throw new Error( "Did not receive data from server." );
        }
      }
    },

    // Handle AJAX Request
    _getResults = function( options ) {
      var urlBuilder = {
        playlist : ( function() {
          return _rdioURL + "/people/" + ( options.person ) + "/playlists/" + options.id + "/" + options.playlist + "/&callback=_loadResults";
        }()),
        album : ( function() {
          return _rdioURL + "/artist/" + ( options.artist ) + "/album/" + options.album + "/&callback=_loadResults";
        }())
      },
      url = urlBuilder[ options.type ];
      Popcorn.getJSONP( url, function( data ) {
        _loadResults( data, options );
      }, false );
    };

    return {
      _setup: function( options ) {
        options.containerid = Popcorn.guid();
        var key = options.containerid,
        container = _container[ key ] = document.createElement( "div" ),
        target = _target[ key ] = document.getElementById( options.target );
        if( !target && Popcorn.plugin.debug ) {
          throw new Error( "Target container could not be found." );
        }
        container.style.display = "none";
        container.innerHTML = "";
        target.appendChild( container );
        _album[ key ] = {
          htmlString: ( options.playlist || "Unknown Source" ) || ( options.album || "Unknown Source" )
        };
        _getResults( options );
      },
      start: function( event, options ) {
        var key = options.containerid,
        container = _container[ key ];
        container.innerHTML = _album[ key ].htmlString;
        container.style.display = "inline";
      },
      end: function( event, options ) {
        container = _container[ options.containerid ];
        container.style.display = "none";
        container.innerHTML = "";
      },
      _teardown: function( options ) {
        var key = options.containerid,
        target = _target[ key ];
        if ( _album[ key ] ) {
          delete _album [ key ];
        }
        target && target.removeChild( _container[ key ] );
      }
    };
  })(),
  {
    manifest: {
      about: {
        name: "Popcorn Rdio Plugin",
        version: "0.1",
        author: "Denise Rigato"
      },
      options: {
        start: {
          elem: "input",
          type: "text",
          label: "In"
        },
        end: {
          elem: "input",
          type: "text",
          label: "Out"
        },
        target: "rdio",
        artist: {
          elem: "input",
          type: "text",
          label: "Artist"
        },
        album: {
          elem: "input",
          type: "text",
          label: "Album"
        },
        person: {
          elem: "input",
          type: "text",
          label: "Person"
        },
        id: {
          elem: "input",
          type: "text",
          label: "Id"
        },
        playlist: {
          elem: "input",
          type: "text",
          label: "Playlist"
        }
      }
    }
  });
}( Popcorn ));
<|endoftext|>