<commit_before>$(function()
  {

    window.Sip = Backbone.Model.extend({

      initialize: function()
        {
          this.loadJobs();
        },

      hasFinished: function()
        {
          return false == this.jobs.some(function(job)
            {
              return -1 < jQuery.inArray(job.get('currentstep'), ['Requires approval', 'Executing command(s)']);
            });
        },

      set: function(attributes, options)
        {
          Backbone.Model.prototype.set.call(this, attributes, options);

          if (undefined !== this.jobs && !this.hasFinished())
          {
            this.view.update();
          }
        },

      loadJobs: function()
        {
          // Nested collection
          this.jobs = new JobCollection(this.get('jobs'));

          var self = this;
          this.jobs.each(function(job)
            {
              job.sip = self;
            });
        }
    });

    window.SipCollection = Backbone.Collection.extend({

      model: Sip,

      url: '/sips/go/',

      initialize: function()
        {

        },

      comparator: function(sip)
        {
          return 0 - sip.get('timestamp');
        }

    });

    window.SipView = Backbone.View.extend({

      className: 'sip',

      template: _.template($('#sip-template').html()),

      events: {
        'click .sip-row > .sip-detail-icon-status > a': 'toggleJobs',
        'click .sip-row > .sip-detail-actions > .btn_show_jobs': 'toggleJobs',
        'click .sip-row > .sip-detail-actions > .btn_delete_sip': 'delete',
      },

      initialize: function()
        {
          _.bindAll(this, 'render', 'update', 'add', 'updateIcon');
          this.model.view = this;
          this.model.bind('change:timestamp', this.update);
        },

      render: function()
        {
          $(this.el).html(this.template(this.model.toJSON()));

          this.$jobContainer = this.$('.sip-detail-job-container');

          return this;
        },

      update: function()
        {
          // Reload nested collection
          this.model.loadJobs(); // .refresh() shouldn't work here

          // Update timestamp
          this.$('.sip-detail-timestamp').html(
            new Date(this.model.get('timestamp') * 1000).getArchivematicaDateTime()
          );

          // Update icon
          this.updateIcon();

          if (this.$jobContainer.is(':visible'))
          {
            this.$jobContainer.empty();

            var self = this;

            this.model.jobs.each(function(job)
              {
                var view = new JobView({model: job});
                self.$jobContainer.append(view.render().el);
              });
          }
        },

      updateIcon: function()
        {
          this.$('.sip-detail-icon-status > a').html(this.model.jobs.getIcon());
        },

      toggleJobs: function(event)
        {
          if (event)
          {
            event.preventDefault();
          }

          if (this.$jobContainer.is(':visible'))
          {
            this.$jobContainer.slideUp('fast');
            $(this.el).removeClass('sip-selected');
          }
          else
          {
            this.$jobContainer.empty();

            var self = this;
            this.model.jobs.each(function(job)
              {
                var view = new JobView({model: job});
                self.$jobContainer.append(view.render().el);
              });

            this.$jobContainer.slideDown('fast');
            $(this.el).addClass('sip-selected');
          }
        },

      delete: function(event)
        {
          event.preventDefault();

          $(this.el).addClass('sip-deleting');

          var self = this;

          $('
' +
              '
Are you sure that you want to permanently delete the selected SIP?
' +
              '
Directory: ' + this.model.get('directory') + '
UUID: ' + this.model.get('uuid') + '
Status: ' + $(this.el).find('.sip-detail-icon-status > a > img').attr('title') + '
' +
            '
').dialog(
            {
              modal: true,
              resizable: false,
              title: false,
              draggable: false,
              title: 'Delete SIP',
              width: 480,
              close: function(event, ui)
                {
                  if (event.which !== undefined)
                  {
                    $(self.el).removeClass('sip-deleting');
                  }
                },
              buttons: [
                  {
                    text: 'Confirm',
                    click: function() {

                      var $dialog = $(this);

                      self.model.destroy({
                        success: function (model, response)
                          {
                            $dialog.dialog('close');

                            setTimeout(function()
                              {
                                $(self.el).hide('blind', function()
                                  {
                                    $(this).remove();
                                  });
                              }, 250);
                          },
                        error: function(model, response)
                          {
                            $dialog.dialog('close');
                            $(self.el).removeClass('sip-deleting');
                          }

                      });
                    }
                  },
                  {
                    text: 'Cancel',
                    click: function() {
                        $(this).dialog('close');
                        $(self.el).removeClass('sip-deleting');
                      }
                  }]
            });
        }
    });

    window.Job = Backbone.Model.extend({

    });

    window.JobCollection = Backbone.Collection.extend({

      model: Job,

      getIcon: function()
        {
          var path = '';
          var title = '';

          if (undefined !== this.find(function(job)
            {
              return 0 < job.get('status') || 'Requires approval' == job.get('currentstep');
            }))
          {
            path = '/media/images/bell.png';
            title = 'Requires approval';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Failed' == job.get('currentstep');
            }))
          {
            path = '/media/images/cancel.png';
            title = 'Failed';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Executing command(s)' == job.get('currentstep');
            }))
          {
            path = '/media/images/icons/arrow_refresh.png';
            title = 'Executing command(s)';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Rejected' == job.get('currentstep');
            }))
          {
            path = '/media/images/icons/control_stop_blue.png';
            title = 'Rejected';
          }
          else
          {
            path = '/media/images/accept.png';
            title = 'Completed successfully';
          }

          return '';
        }

    });

    window.JobView = Backbone.View.extend({

      className: 'job',

      events: {
        'click .btn_browse_job': 'browseJob',
        'click .btn_approve_job': 'approveJob',
        'click .btn_reject_job': 'rejectJob',
        'click .btn_show_tasks': 'showTasks',
      },

      template: _.template($('#job-template').html()),

      initialize: function()
        {
          _.bindAll(this, 'render', 'approveJob', 'rejectJob');
          this.model.bind('change', this.render);
          this.model.view = this;
        },

      render: function()
        {
          $(this.el).html(this.template(this.model.toJSON()));

          if (-1 < jQuery.inArray(this.model.get('currentstep'), ['Requires approval', 'Failed', 'Rejected']))
          {
            $(this.el).css('background-color', '#f2d8d8');
          }
          else if ('Executing command(s)' == this.model.get('currentstep'))
          {
            $(this.el).css('background-color', '#fedda7');
          }
          else
          {
            $(this.el).css('background-color', '#d8f2dc');
          }

          if (1 == this.model.get('status'))
          {
            this.$('.job-detail-actions')
              .append('Browse')
              .append('Approve')
              .append('Reject')
          }

          this.$('.job-detail-microservice > a').tooltip();

          return this;
        },

      showTasks: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            type: 'GET',
            dataType: 'html',
            success: function(data)
              {
                $('

')
                  .append('
' + $(data).find('tbody').html() + '
')
                  .dialog({
                    title: this.model.sip.get('directory') + ' &raquo ' + this.model.get('microservice') + ' &raquo Tasks',
                    width: 640,
                    height: 480,
                    modal: true,
                    buttons: [
                      {
                        text: 'Close',
                        click: function() { $(this).dialog('close'); }
                      }]
                  });
              },
            url: '/tasks/' + this.model.get('uuid') + '/'
          });
        },

      browseJob: function(event)
        {
          event.preventDefault();

          alert("browsejob " + this.model.get('uuid'));
        },

      approveJob: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            data: { uuid: this.model.get('uuid') },
            type: 'POST',
            success: function(data)
              {
                this.model.set({
                  'currentstep': 'Executing command(s)',
                  'status': 0
                });

                this.model.sip.view.updateIcon();
              },
            url: '/mcp/approve-job/'
          });
        },

      rejectJob: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            data: { uuid: this.model.get('uuid') },
            type: 'POST',
            success: function(data)
              {
                this.model.set({
                  'currentstep': 'Rejected',
                  'status': 0
                });

                this.model.sip.view.updateIcon();
                // this.model.sip.view.toggleJobs();
              },
            url: '/mcp/reject-job/'
          });
        },

    });

    window.StatusView = Backbone.View.extend({

      id: 'status',

      template: _.template($('#status-template').html()),

      initialize: function()
        {
          this.render();
        },

      render: function()
        {
          $(this.el).html(this.template()).hide().appendTo('body');

          return this;
        },

      hide: function()
        {
          var self = this;
          setTimeout(function()
            {
              $(self.el).fadeOut('fast');
            }, 1000);
        },

      text: function(message, error)
        {
          $(this.el).show().find('span').html(message);

          if (true === error)
          {
            $(this.el).addClass('status-error');
          }
          else
          {
            $(this.el).removeClass('status-error');
          }
        }

    });

    window.AppView = Backbone.View.extend({

      el: $('#sip-container'),

      initialize: function()
        {
          _.bindAll(this, 'add', 'remove');
          Sips.bind('add', this.add);
          Sips.bind('remove', this.remove);

          window.statusWidget = new window.StatusView();

          this.poll(true);
        },

      add: function(sip)
        {
          var view = new SipView({model: sip});
          var $new = $(view.render().el).hide();

          // Get the current position in the collection
          var position = Sips.indexOf(sip);

          if (0 == position)
          {
            this.el.children('#sip-body').prepend($new);
          }
          else
          {
            var $target = this.el.find('.sip').eq(position);

            if ($target.length)
            {
              $target.before($new);
            }
            else
            {
              this.el.children('#sip-body').append($new);
            }
          }

          if (!this.firstPoll)
          {
            // Animation
            $new.addClass('sip-new').show('blind', {}, 500, function()
              {
                $(this).removeClass('sip-new', 2000);
              });
          }
          else
          {
            $new.show();
          }
        },

      remove: function(sip)
        {
          $(sip.view.el).hide('blind', function()
            {
              $(this).remove();
            });
        },

      poll: function(start)
        {
          this.firstPoll = undefined !== start;

          $.ajax({
            context: this,
            dataType: 'json',
            type: 'GET',
            url: '/sips/go/',
            beforeSend: function()
              {
                window.statusWidget.text(undefined !== start ? 'Loading...' : 'Refreshing...');
              },
            error: function()
              {
                window.statusWidget.text('Error trying to connect to database. Trying again...', true);
              },
            success: function(response)
              {
                var objects = response.objects;

                for (var i in objects)
                {
                  var sip = objects[i];
                  var item = Sips.find(function(item) { return item.get('uuid') == sip.uuid; });

                  if (undefined === item)
                  {
                    // Add new sips
                    Sips.add(sip);
                  }
                  else
                  {
                    // Update sips
                    item.set(sip);
                  }
                }

                // Delete sips
                if (Sips.length > objects.length)
                {
                  var unusedSips = Sips.reject(function(sip)
                      {
                        return -1 < $.inArray(sip.get('uuid'), _.pluck(objects, 'uuid'));
                      });

                  Sips.remove(unusedSips);
                }

                // MCP status
                if (response.mcp)
                {
                  window.statusWidget.hide();
                }
                else
                {
                  window.statusWidget.text('Error trying to connect to MCP server. Trying again...', true);
                }
              },
            complete: function()
              {
                var self = this;
                setTimeout(function()
                  {
                    self.poll();
                  }, window.pollingInterval ? window.pollingInterval * 1000: 5000);
              }
          });
        },

    });

    $.fn.tooltip = function(options)
      {
        var settings = {
          xOffset: 10,
          yOffset: 20,
          width: 280
        };

        return this.each(function()
          {
            var $this = $(this);
            var $tooltip;

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

            if (undefined === settings.content)
            {
              settings.content = $this.attr('title');
            }

            $this
              .attr('title', '')
              .mouseover(function(event)
                {
                  $tooltip = $('
' + (undefined !== settings.title ? '
' + settings.title + '
' : '') + '
' + settings.content + '

')
                    .hide()
                    .css({
                      top: (event.pageY - settings.xOffset) + 'px',
                      left: (event.pageX + settings.yOffset) + 'px',
                      width: settings.width + 'px'})
                    .fadeIn()
                    .appendTo('body');
                })
              .mouseout(function(event)
                {
                  $tooltip.remove();
                })
              .mousemove(function(event)
                {
                  $tooltip.css({
                    top: (event.pageY - settings.xOffset) + 'px',
                    left: (event.pageX + settings.yOffset) + 'px'});
                })
              .click(function(event)
                {
                  event.preventDefault();
                });
          });
      };

    Date.prototype.getArchivematicaDateTime = function()
      {
        pad = function (n)
          {
            return n < 10 ? '0' + n : n;
          }

        return this.getUTCFullYear() + '-' + pad(this.getUTCMonth() + 1) + '-' + pad(this.getUTCDate()) + ' ' + pad(this.getUTCHours()) + ':' + pad(this.getUTCMinutes()); // + ':' + pad(this.getUTCSeconds());
      };
  }
);<commit_msg>Don't use delete statemet because is a reserver word in JavaScript. Fixes compatibility issue in Safari and IE.<commit_after>$(function()
  {

    window.Sip = Backbone.Model.extend({

      initialize: function()
        {
          this.loadJobs();
        },

      hasFinished: function()
        {
          return false == this.jobs.some(function(job)
            {
              return -1 < jQuery.inArray(job.get('currentstep'), ['Requires approval', 'Executing command(s)']);
            });
        },

      set: function(attributes, options)
        {
          Backbone.Model.prototype.set.call(this, attributes, options);

          if (undefined !== this.jobs && !this.hasFinished())
          {
            this.view.update();
          }
        },

      loadJobs: function()
        {
          // Nested collection
          this.jobs = new JobCollection(this.get('jobs'));

          var self = this;
          this.jobs.each(function(job)
            {
              job.sip = self;
            });
        }
    });

    window.SipCollection = Backbone.Collection.extend({

      model: Sip,

      url: '/sips/go/',

      initialize: function()
        {

        },

      comparator: function(sip)
        {
          return 0 - sip.get('timestamp');
        }

    });

    window.SipView = Backbone.View.extend({

      className: 'sip',

      template: _.template($('#sip-template').html()),

      events: {
        'click .sip-row > .sip-detail-icon-status > a': 'toggleJobs',
        'click .sip-row > .sip-detail-actions > .btn_show_jobs': 'toggleJobs',
        'click .sip-row > .sip-detail-actions > .btn_delete_sip': 'remove',
      },

      initialize: function()
        {
          _.bindAll(this, 'render', 'update', 'add', 'updateIcon');
          this.model.view = this;
          this.model.bind('change:timestamp', this.update);
        },

      render: function()
        {
          $(this.el).html(this.template(this.model.toJSON()));

          this.$jobContainer = this.$('.sip-detail-job-container');

          return this;
        },

      update: function()
        {
          // Reload nested collection
          this.model.loadJobs(); // .refresh() shouldn't work here

          // Update timestamp
          this.$('.sip-detail-timestamp').html(
            new Date(this.model.get('timestamp') * 1000).getArchivematicaDateTime()
          );

          // Update icon
          this.updateIcon();

          if (this.$jobContainer.is(':visible'))
          {
            this.$jobContainer.empty();

            var self = this;

            this.model.jobs.each(function(job)
              {
                var view = new JobView({model: job});
                self.$jobContainer.append(view.render().el);
              });
          }
        },

      updateIcon: function()
        {
          this.$('.sip-detail-icon-status > a').html(this.model.jobs.getIcon());
        },

      toggleJobs: function(event)
        {
          if (event)
          {
            event.preventDefault();
          }

          if (this.$jobContainer.is(':visible'))
          {
            this.$jobContainer.slideUp('fast');
            $(this.el).removeClass('sip-selected');
          }
          else
          {
            this.$jobContainer.empty();

            var self = this;
            this.model.jobs.each(function(job)
              {
                var view = new JobView({model: job});
                self.$jobContainer.append(view.render().el);
              });

            this.$jobContainer.slideDown('fast');
            $(this.el).addClass('sip-selected');
          }
        },

      remove: function(event)
        {
          event.preventDefault();

          $(this.el).addClass('sip-deleting');

          var self = this;

          $('
' +
              '
Are you sure that you want to permanently delete the selected SIP?
' +
              '
Directory: ' + this.model.get('directory') + '
UUID: ' + this.model.get('uuid') + '
Status: ' + $(this.el).find('.sip-detail-icon-status > a > img').attr('title') + '
' +
            '
').dialog(
            {
              modal: true,
              resizable: false,
              title: false,
              draggable: false,
              title: 'Delete SIP',
              width: 480,
              close: function(event, ui)
                {
                  if (event.which !== undefined)
                  {
                    $(self.el).removeClass('sip-deleting');
                  }
                },
              buttons: [
                  {
                    text: 'Confirm',
                    click: function() {

                      var $dialog = $(this);

                      self.model.destroy({
                        success: function (model, response)
                          {
                            $dialog.dialog('close');

                            setTimeout(function()
                              {
                                $(self.el).hide('blind', function()
                                  {
                                    $(this).remove();
                                  });
                              }, 250);
                          },
                        error: function(model, response)
                          {
                            $dialog.dialog('close');
                            $(self.el).removeClass('sip-deleting');
                          }

                      });
                    }
                  },
                  {
                    text: 'Cancel',
                    click: function() {
                        $(this).dialog('close');
                        $(self.el).removeClass('sip-deleting');
                      }
                  }]
            });
        }
    });

    window.Job = Backbone.Model.extend({

    });

    window.JobCollection = Backbone.Collection.extend({

      model: Job,

      getIcon: function()
        {
          var path = '';
          var title = '';

          if (undefined !== this.find(function(job)
            {
              return 0 < job.get('status') || 'Requires approval' == job.get('currentstep');
            }))
          {
            path = '/media/images/bell.png';
            title = 'Requires approval';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Failed' == job.get('currentstep');
            }))
          {
            path = '/media/images/cancel.png';
            title = 'Failed';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Executing command(s)' == job.get('currentstep');
            }))
          {
            path = '/media/images/icons/arrow_refresh.png';
            title = 'Executing command(s)';
          }
          else if (undefined !== this.find(function(job)
            {
              return 'Rejected' == job.get('currentstep');
            }))
          {
            path = '/media/images/icons/control_stop_blue.png';
            title = 'Rejected';
          }
          else
          {
            path = '/media/images/accept.png';
            title = 'Completed successfully';
          }

          return '';
        }

    });

    window.JobView = Backbone.View.extend({

      className: 'job',

      events: {
        'click .btn_browse_job': 'browseJob',
        'click .btn_approve_job': 'approveJob',
        'click .btn_reject_job': 'rejectJob',
        'click .btn_show_tasks': 'showTasks',
      },

      template: _.template($('#job-template').html()),

      initialize: function()
        {
          _.bindAll(this, 'render', 'approveJob', 'rejectJob');
          this.model.bind('change', this.render);
          this.model.view = this;
        },

      render: function()
        {
          $(this.el).html(this.template(this.model.toJSON()));

          if (-1 < jQuery.inArray(this.model.get('currentstep'), ['Requires approval', 'Failed', 'Rejected']))
          {
            $(this.el).css('background-color', '#f2d8d8');
          }
          else if ('Executing command(s)' == this.model.get('currentstep'))
          {
            $(this.el).css('background-color', '#fedda7');
          }
          else
          {
            $(this.el).css('background-color', '#d8f2dc');
          }

          if (1 == this.model.get('status'))
          {
            this.$('.job-detail-actions')
              .append('Browse')
              .append('Approve')
              .append('Reject')
          }

          this.$('.job-detail-microservice > a').tooltip();

          return this;
        },

      showTasks: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            type: 'GET',
            dataType: 'html',
            success: function(data)
              {
                $('

')
                  .append('
' + $(data).find('tbody').html() + '
')
                  .dialog({
                    title: this.model.sip.get('directory') + ' &raquo ' + this.model.get('microservice') + ' &raquo Tasks',
                    width: 640,
                    height: 480,
                    modal: true,
                    buttons: [
                      {
                        text: 'Close',
                        click: function() { $(this).dialog('close'); }
                      }]
                  });
              },
            url: '/tasks/' + this.model.get('uuid') + '/'
          });
        },

      browseJob: function(event)
        {
          event.preventDefault();

          alert("browsejob " + this.model.get('uuid'));
        },

      approveJob: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            data: { uuid: this.model.get('uuid') },
            type: 'POST',
            success: function(data)
              {
                this.model.set({
                  'currentstep': 'Executing command(s)',
                  'status': 0
                });

                this.model.sip.view.updateIcon();
              },
            url: '/mcp/approve-job/'
          });
        },

      rejectJob: function(event)
        {
          event.preventDefault();

          $.ajax({
            context: this,
            data: { uuid: this.model.get('uuid') },
            type: 'POST',
            success: function(data)
              {
                this.model.set({
                  'currentstep': 'Rejected',
                  'status': 0
                });

                this.model.sip.view.updateIcon();
                // this.model.sip.view.toggleJobs();
              },
            url: '/mcp/reject-job/'
          });
        },

    });

    window.StatusView = Backbone.View.extend({

      id: 'status',

      template: _.template($('#status-template').html()),

      initialize: function()
        {
          this.render();
        },

      render: function()
        {
          $(this.el).html(this.template()).hide().appendTo('body');

          return this;
        },

      hide: function()
        {
          var self = this;
          setTimeout(function()
            {
              $(self.el).fadeOut('fast');
            }, 1000);
        },

      text: function(message, error)
        {
          $(this.el).show().find('span').html(message);

          if (true === error)
          {
            $(this.el).addClass('status-error');
          }
          else
          {
            $(this.el).removeClass('status-error');
          }
        }

    });

    window.AppView = Backbone.View.extend({

      el: $('#sip-container'),

      initialize: function()
        {
          _.bindAll(this, 'add', 'remove');
          Sips.bind('add', this.add);
          Sips.bind('remove', this.remove);

          window.statusWidget = new window.StatusView();

          this.poll(true);
        },

      add: function(sip)
        {
          var view = new SipView({model: sip});
          var $new = $(view.render().el).hide();

          // Get the current position in the collection
          var position = Sips.indexOf(sip);

          if (0 == position)
          {
            this.el.children('#sip-body').prepend($new);
          }
          else
          {
            var $target = this.el.find('.sip').eq(position);

            if ($target.length)
            {
              $target.before($new);
            }
            else
            {
              this.el.children('#sip-body').append($new);
            }
          }

          if (!this.firstPoll)
          {
            // Animation
            $new.addClass('sip-new').show('blind', {}, 500, function()
              {
                $(this).removeClass('sip-new', 2000);
              });
          }
          else
          {
            $new.show();
          }
        },

      remove: function(sip)
        {
          $(sip.view.el).hide('blind', function()
            {
              $(this).remove();
            });
        },

      poll: function(start)
        {
          this.firstPoll = undefined !== start;

          $.ajax({
            context: this,
            dataType: 'json',
            type: 'GET',
            url: '/sips/go/',
            beforeSend: function()
              {
                window.statusWidget.text(undefined !== start ? 'Loading...' : 'Refreshing...');
              },
            error: function()
              {
                window.statusWidget.text('Error trying to connect to database. Trying again...', true);
              },
            success: function(response)
              {
                var objects = response.objects;

                for (var i in objects)
                {
                  var sip = objects[i];
                  var item = Sips.find(function(item) { return item.get('uuid') == sip.uuid; });

                  if (undefined === item)
                  {
                    // Add new sips
                    Sips.add(sip);
                  }
                  else
                  {
                    // Update sips
                    item.set(sip);
                  }
                }

                // Delete sips
                if (Sips.length > objects.length)
                {
                  var unusedSips = Sips.reject(function(sip)
                      {
                        return -1 < $.inArray(sip.get('uuid'), _.pluck(objects, 'uuid'));
                      });

                  Sips.remove(unusedSips);
                }

                // MCP status
                if (response.mcp)
                {
                  window.statusWidget.hide();
                }
                else
                {
                  window.statusWidget.text('Error trying to connect to MCP server. Trying again...', true);
                }
              },
            complete: function()
              {
                var self = this;
                setTimeout(function()
                  {
                    self.poll();
                  }, window.pollingInterval ? window.pollingInterval * 1000: 5000);
              }
          });
        },

    });

    $.fn.tooltip = function(options)
      {
        var settings = {
          xOffset: 10,
          yOffset: 20,
          width: 280
        };

        return this.each(function()
          {
            var $this = $(this);
            var $tooltip;

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

            if (undefined === settings.content)
            {
              settings.content = $this.attr('title');
            }

            $this
              .attr('title', '')
              .mouseover(function(event)
                {
                  $tooltip = $('
' + (undefined !== settings.title ? '
' + settings.title + '
' : '') + '
' + settings.content + '

')
                    .hide()
                    .css({
                      top: (event.pageY - settings.xOffset) + 'px',
                      left: (event.pageX + settings.yOffset) + 'px',
                      width: settings.width + 'px'})
                    .fadeIn()
                    .appendTo('body');
                })
              .mouseout(function(event)
                {
                  $tooltip.remove();
                })
              .mousemove(function(event)
                {
                  $tooltip.css({
                    top: (event.pageY - settings.xOffset) + 'px',
                    left: (event.pageX + settings.yOffset) + 'px'});
                })
              .click(function(event)
                {
                  event.preventDefault();
                });
          });
      };

    Date.prototype.getArchivematicaDateTime = function()
      {
        pad = function (n)
          {
            return n < 10 ? '0' + n : n;
          }

        return this.getUTCFullYear() + '-' + pad(this.getUTCMonth() + 1) + '-' + pad(this.getUTCDate()) + ' ' + pad(this.getUTCHours()) + ':' + pad(this.getUTCMinutes()); // + ':' + pad(this.getUTCSeconds());
      };
  }
);<|endoftext|>


<commit_before>(function(env) {
    env.ddg_spice_meta_cpan = function(api_result) {
        "use strict";

        if (!(api_result && api_result.author && api_result.version)) {
            return Spice.failed('meta_cpan');
        }
        var script = $('[src*="/js/spice/meta_cpan/"]')[0],
            source = $(script).attr("src"),
            query = decodeURIComponent(source.match(/meta_cpan\/([^\/]+)/)[1]),
            link = "search?q=" + encodeURIComponent(query),
            module_name = api_result.name;

        if (api_result.module && api_result.module.length > 0) {
            module_name = api_result.module[0].name;
            if (api_result.module[0].associated_pod) {
                link = "module/" + api_result.module[0].associated_pod;
            }
        }

        Spice.add({
            id: "meta_cpan",
            name: "Software",
            data: {
                title: module_name,
                subtitle: api_result.abstract,
                record_data: api_result,
                record_keys: ['author','version','description']
            },
            meta: {
                sourceName: "MetaCPAN",
                sourceUrl: 'https://metacpan.org/' + link
            },
            templates: {
                group: 'list',
                options: {
                    content: 'record',
                    moreAt: true
                }
            }
        });
    };
}(this));
<commit_msg>Remove old data retrieval, replace error condition<commit_after>(function(env) {
    env.ddg_spice_meta_cpan = function(api_result) {
        "use strict";

        if ( !api_result || api_result.hits.hits.length < 1 ) {
            return DDH.failed('meta_cpan');
        }

        Spice.add({
            id: "meta_cpan",
            name: "Software",
            data: {
                title: module_name,
                subtitle: api_result.abstract,
                record_data: api_result,
                record_keys: ['author','version','description']
            },
            meta: {
                sourceName: "MetaCPAN",
                sourceUrl: 'https://metacpan.org/' + link
            },
            templates: {
                group: 'list',
                options: {
                    content: 'record',
                    moreAt: true
                }
            }
        });
    };
}(this));
<|endoftext|>


<commit_before>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (sceneLayout) {
        sceneLayout._scene.spikeEnable = !sceneLayout._scene.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handle3dCamera (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    if (attr === 'reset') {
        // Reset camera position to initial value, go in rotate mode
        val = 'rotate';
        var cameraPositionInitial = layout.scene._cameraPositionInitial;
        layoutUpdate = {
            'dragmode': 'rotate',
            'scene.cameraposition': $.extend(true, [], cameraPositionInitial)
        };
    } else {
        layoutUpdate[attr] = val;
    }

    scenes.forEach( function (sceneLayout) {
        if ('_scene' in sceneLayout && 'camera' in sceneLayout._scene) {
            sceneLayout._scene.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    resetCamera3d: {
        title: 'Reset camera',
        attr: 'reset',
        val: '',
        icon: 'icon-home',
        click: handle3dCamera
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handle3dCamera
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handle3dCamera
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handle3dCamera
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<commit_msg>split handle3dCamera into handleCamera3d and handleDrag3d<commit_after>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (sceneLayout) {
        sceneLayout._scene.spikeEnable = !sceneLayout._scene.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleDrag3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // update the webgl3D key binding
    scenes.forEach( function (sceneLayout) {
        if ('_scene' in sceneLayout && 'camera' in sceneLayout._scene) {
            sceneLayout._scene.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleCamera3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        }),
        _scene = layout.scene._scene;

    if (attr === 'resetDefault') {
        // Reset camera position to default
        _scene.setCameraToDefault();
    } else if (attr === 'resetLastSave') {
        // Reset camera back to the position at the last save
        var cameraPositionLastSave = _scene._cameraPositionLastSave;
        _scene.setCameraPosition(cameraPositionLastSave);
    }

    /**
     * TODO multiple scenes!
     * Ideally, in a multiple scene plot, the modebar buttons should
     * reset the camera position of the scene last moved.
     */
}

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    resetCamera3d: {
        title: 'Reset camera',
        attr: 'reset',
        val: '',
        icon: 'icon-home',
        click: handle3dCamera
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handle3dCamera
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handle3dCamera
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handle3dCamera
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<|endoftext|>


<commit_before>// get the canvas element and its context
var canvas = document.getElementById('sketch');
var context = canvas.getContext('2d');

// the aspect ratio is always based on 1140x400, height is calculated from width:
canvas.width = $('#sketchContainer').outerWidth();
canvas.height = (canvas.width/1140)*400;
$('#sketchContainer').outerHeight(String(canvas.height) + "px", true);
// scale function needs to know the width/height pre-resizing:
var oWidth = canvas.width;
var oHeight = canvas.height;
var lines = [];

var lastMouse = {
  x: 0,
  y: 0
};

// brush settings
context.lineWidth = 2;
context.lineJoin = 'round';
context.lineCap = 'round';
context.strokeStyle = '#000';

// attach the mousedown, mouseout, mousemove, mouseup event listeners.
canvas.addEventListener('mousedown', function (e) {
  lastMouse = {
    x: e.pageX - this.offsetLeft,
    y: e.pageY - this.offsetTop
  };
  canvas.addEventListener('mousemove', move, false);
}, false);

canvas.addEventListener('mouseout', function () {
  canvas.removeEventListener('mousemove', move, false);
}, false);

canvas.addEventListener('mouseup', function () {
  canvas.removeEventListener('mousemove', move, false);
}, false);

// Sets the brush size:
function setSize(size) {
  context.lineWidth = size;
}

// Sets the brush color:
function setColor(color) {
  context.globalCompositeOperation = 'source-over';
  context.strokeStyle = color;
}

// Sets the brush to erase-mode:
function eraser() {
  context.globalCompositeOperation = 'destination-out';
  context.strokeStyle = 'rgba(0,0,0,1)';
}

// Clears the canvas and the lines-array:
function clear(send) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  lines = [];
  if (send && TogetherJS.running) {
    TogetherJS.send({
      type: 'clear'
    });
  }
}

// Redraws the lines from the lines-array:
function reDraw(lines){
  for (var i in lines) {
    draw(lines[i][0], lines[i][1], lines[i][2], lines[i][3], lines[i][4], false);
  }
}
// Draws the lines, called by move and the TogetherJS event listener:
function draw(start, end, color, size, compositeOperation, save) {
  context.save();
  context.lineJoin = 'round';
  context.lineCap = 'round';
  // Since the coordinates have been translated to an 1140x400 canvas, the context needs to be scaled before it can be drawn on:
  context.scale(canvas.width/1140,canvas.height/400);
  context.strokeStyle = color;
  context.globalCompositeOperation = compositeOperation;
  context.lineWidth = size;
  context.beginPath();
  context.moveTo(start.x, start.y);
  context.lineTo(end.x, end.y);
  context.closePath();
  context.stroke();
  context.restore();
  if (save) {
    // Won't save if draw() is called from reDraw().
    lines.push([{x: start.x, y: start.y}, {x: end.x, y: end.y}, color, size, compositeOperation]);
  }
}

// Called whenever the mousemove event is fired, calls the draw function:
function move(e) {
  var mouse = {
    x: e.pageX - this.offsetLeft,
    y: e.pageY - this.offsetTop
  };
  // Translates the coordinates from the local canvas size to 1140x400:
  sendMouse = {
    x: (1140/canvas.width)*mouse.x,
    y: (400/canvas.height)*mouse.y
  };
  sendLastMouse = {
    x: (1140/canvas.width)*lastMouse.x,
    y: (400/canvas.height)*lastMouse.y
  };
  draw(sendLastMouse, sendMouse, context.strokeStyle, context.lineWidth, context.globalCompositeOperation, true);
  if (TogetherJS.running) {
    TogetherJS.send({
      type: 'draw',
      start: sendLastMouse,
      end: sendMouse,
      color: context.strokeStyle,
      size: context.lineWidth,
      compositeOperation: context.globalCompositeOperation
    });
  }
  lastMouse = mouse;
}

// Listens for draw messages, sends info about the drawn lines:
TogetherJS.hub.on('draw', function (msg) {
  if (!msg.sameUrl) {
      return;
  }
  draw(msg.start, msg.end, msg.color, msg.size, msg.compositeOperation, true);
});

// Clears the canvas whenever someone presses the clear-button
TogetherJS.hub.on('clear', function (msg) {
  if (!msg.sameUrl) {
    return;
  }
  clear(false);
});

// Hello is sent from every newly connected user, this way they will receive what has already been drawn:
TogetherJS.hub.on('togetherjs.hello', function () {
  TogetherJS.send({
    type: 'init',
    lines: lines
  });
});

// Draw initially received drawings:
TogetherJS.hub.on('init', function (msg) {
  reDraw(msg.lines);
  lines = msg.lines;
});

// JQuery to handle buttons and resizing events, also changes the cursor to a dot resembling the brush size:
$(document).ready(function () {
  // changeMouse creates a temporary invisible canvas that shows the cursor, which is then set as the cursor through css:
  function changeMouse() {
    // Makes sure the cursorSize is scaled:
    var cursorSize = context.lineWidth*(canvas.width/1140);
    if (cursorSize < 10){
        cursorSize = 10;
    }
    var cursorColor = context.strokeStyle;
    var cursorGenerator = document.createElement('canvas');
    cursorGenerator.width = cursorSize;
    cursorGenerator.height = cursorSize;
    var ctx = cursorGenerator.getContext('2d');

    var centerX = cursorGenerator.width/2;
    var centerY = cursorGenerator.height/2;

    ctx.beginPath();
    ctx.arc(centerX, centerY, (cursorSize/2)-4, 0, 2 * Math.PI, false);
    ctx.lineWidth = 3;
    ctx.strokeStyle = cursorColor;
    ctx.stroke();
    $('#sketch').css('cursor', 'url(' + cursorGenerator.toDataURL('image/png') + ') ' + cursorSize/2 + ' ' + cursorSize/2 + ',crosshair');
  }
  // Init mouse
  changeMouse();

  // Redraws the lines whenever the canvas is resized:
  $(window).resize(function() {
    if ($('#sketchContainer').width() != oWidth) {
      canvas.width = $('#sketchContainer').width();
      canvas.height = (canvas.width/1140)*400;
      $('#sketchContainer').outerHeight(String(canvas.height)+"px", true);
      var ratio = canvas.width/oWidth;
      oWidth = canvas.width;
      oHeight = canvas.height;
      reDraw(lines);
      changeMouse();
    }
  });

  // Clears the canvas:
  $('.clear').click(function () {
    clear(true);
  });

  // Color-button functions:
  $('.color-picker').click(function () {
    var $this = $(this);
    console.log($this);
    setColor($this.css("background-color"));
    changeMouse();
  });

  $('.eraser').click(function () {
    eraser();
    changeMouse();
  });
  // TogetherJS user color:
  $('.user-color-pick').click(function() {
    setColor(TogetherJS.require('peers').Self.color);
    changeMouse();
  });

  // Increase/decrease brush size:
  $('.plus-size').click(function() {
    setSize(context.lineWidth+3);
    changeMouse();
  });

  $('.minus-size').click(function() {
    if (context.lineWidth > 3) {
      setSize(context.lineWidth-3);
    }
    changeMouse();
  });
});
<commit_msg>Add support for touch events to the drawing demo<commit_after>// get the canvas element and its context
var canvas = document.getElementById('sketch');
var context = canvas.getContext('2d');

// the aspect ratio is always based on 1140x400, height is calculated from width:
canvas.width = $('#sketchContainer').outerWidth();
canvas.height = (canvas.width/1140)*400;
$('#sketchContainer').outerHeight(String(canvas.height) + "px", true);
// scale function needs to know the width/height pre-resizing:
var oWidth = canvas.width;
var oHeight = canvas.height;
var lines = [];

var lastMouse = {
  x: 0,
  y: 0
};

var ongoingTouches = [];

// brush settings
context.lineWidth = 2;
context.lineJoin = 'round';
context.lineCap = 'round';
context.strokeStyle = '#000';

// attach the mousedown, mouseout, mousemove, mouseup event listeners.
canvas.addEventListener('mousedown', function (e) {
  lastMouse = {
    x: e.pageX - this.offsetLeft,
    y: e.pageY - this.offsetTop
  };
  canvas.addEventListener('mousemove', move, false);
}, false);

canvas.addEventListener('mouseout', function () {
  canvas.removeEventListener('mousemove', move, false);
}, false);

canvas.addEventListener('mouseup', function () {
  canvas.removeEventListener('mousemove', move, false);
}, false);

// attach the touchstart, touchend, touchcancel, touchleave,
// and touchmove event listeners.
canvas.addEventListener('touchstart', touchstart, false);
canvas.addEventListener('touchend', touchend, false);
canvas.addEventListener('touchcancel', touchcancel, false);
canvas.addEventListener('touchleave', touchend, false);
canvas.addEventListener('touchmove', touchmove, false);

// Sets the brush size:
function setSize(size) {
  context.lineWidth = size;
}

// Sets the brush color:
function setColor(color) {
  context.globalCompositeOperation = 'source-over';
  context.strokeStyle = color;
}

// Sets the brush to erase-mode:
function eraser() {
  context.globalCompositeOperation = 'destination-out';
  context.strokeStyle = 'rgba(0,0,0,1)';
}

// Clears the canvas and the lines-array:
function clear(send) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  lines = [];
  if (send && TogetherJS.running) {
    TogetherJS.send({
      type: 'clear'
    });
  }
}

// Redraws the lines from the lines-array:
function reDraw(lines){
  for (var i in lines) {
    draw(lines[i][0], lines[i][1], lines[i][2], lines[i][3], lines[i][4], false);
  }
}
// Draws the lines, called by move and the TogetherJS event listener:
function draw(start, end, color, size, compositeOperation, save) {
  context.save();
  context.lineJoin = 'round';
  context.lineCap = 'round';
  // Since the coordinates have been translated to an 1140x400 canvas, the context needs to be scaled before it can be drawn on:
  context.scale(canvas.width/1140,canvas.height/400);
  context.strokeStyle = color;
  context.globalCompositeOperation = compositeOperation;
  context.lineWidth = size;
  context.beginPath();
  context.moveTo(start.x, start.y);
  context.lineTo(end.x, end.y);
  context.closePath();
  context.stroke();
  context.restore();
  if (save) {
    // Won't save if draw() is called from reDraw().
    lines.push([{x: start.x, y: start.y}, {x: end.x, y: end.y}, color, size, compositeOperation]);
  }
}

// Called whenever the mousemove event is fired, calls the draw function:
function move(e) {
  var mouse = {
    x: e.pageX - this.offsetLeft,
    y: e.pageY - this.offsetTop
  };
  // Translates the coordinates from the local canvas size to 1140x400:
  sendMouse = {
    x: (1140/canvas.width)*mouse.x,
    y: (400/canvas.height)*mouse.y
  };
  sendLastMouse = {
    x: (1140/canvas.width)*lastMouse.x,
    y: (400/canvas.height)*lastMouse.y
  };
  draw(sendLastMouse, sendMouse, context.strokeStyle, context.lineWidth, context.globalCompositeOperation, true);
  if (TogetherJS.running) {
    TogetherJS.send({
      type: 'draw',
      start: sendLastMouse,
      end: sendMouse,
      color: context.strokeStyle,
      size: context.lineWidth,
      compositeOperation: context.globalCompositeOperation
    });
  }
  lastMouse = mouse;
}

// Convenience method to convert touch position to 1140x400 position
function convertTouch(touch) {
  return {
    x: (1140 / canvas.width) * (touch.pageX - canvas.offsetLeft),
    y: (400 / canvas.height) * (touch.pageY - canvas.offsetTop),
    identifier: touch.identifier
  };
}

// Do a linear search for an ongoing touch with a particular identifier
// Return the index into ongoingTouches, or -1 if not found
function searchOngoingTouches(identifier) {
  for (var i = 0; i < ongoingTouches.length; i++) {
    if (ongoingTouches[i].identifier == identifier) {
      return i;
    }
  }
  return -1;
}

// Called whenever the touchstart event is fired
function touchstart(e) {
  e.preventDefault();
  var touches = e.changedTouches;
  for (var i = 0; i < touches.length; i++) {
    var idx = searchOngoingTouches(touches[i].identifier);
    ongoingTouches.push(convertTouch(touches[i]));
  }
}

// Called whenever the touchmove event is fired, calls the draw function
function touchmove(e) {
  e.preventDefault();
  var touches = e.changedTouches;
  for (var i = 0; i < touches.length; i++) {
    var idx = searchOngoingTouches(touches[i].identifier);
    if (idx >= 0) {
      var lastTouch = ongoingTouches[idx];
      var touch = convertTouch(touches[i]);
      draw(lastTouch, touch, context.strokeStyle, context.lineWidth, context.globalCompositeOperation, true);
      if (TogetherJS.running) {
        TogetherJS.send({
          type: 'draw',
          start: lastTouch,
          end: touch,
          color: context.strokeStyle,
          size: context.lineWidth,
          compositeOperation: context.globalCompositeOperation
        });
      }
      ongoingTouches.splice(idx, 1, touch);
    }
  }
}

// Called whenever touchend or touchleave events are fired
function touchend(e) {
  e.preventDefault();
  var touches = e.changedTouches;
  for (var i = 0; i < touches.length; i++) {
    var idx = searchOngoingTouches(touches[i].identifier);
    if (idx >= 0) {
      var lastTouch = ongoingTouches[idx];
      var touch = convertTouch(touches[i]);
      draw(lastTouch, touch, context.strokeStyle, context.lineWidth, context.globalCompositeOperation, true);
      if (TogetherJS.running) {
        TogetherJS.send({
          type: 'draw',
          start: lastTouch,
          end: touch,
          color: context.strokeStyle,
          size: context.lineWidth,
          compositeOperation: context.globalCompositeOperation
        });
      }
      ongoingTouches.splice(idx, 1);
    }
  }
}

// Called whenever touchcancel event is fired
function touchcancel(e) {
  e.preventDefault();
  var touches = e.changedTouches;
  for (var i = 0; i < touches.length; i++) {
    var idx = searchOngoingTouches(touches[i].identifier);
    if (idx >= 0) {
      ongoingTouches.splice(idx, 1);
    }
  }
}

// Listens for draw messages, sends info about the drawn lines:
TogetherJS.hub.on('draw', function (msg) {
  if (!msg.sameUrl) {
      return;
  }
  draw(msg.start, msg.end, msg.color, msg.size, msg.compositeOperation, true);
});

// Clears the canvas whenever someone presses the clear-button
TogetherJS.hub.on('clear', function (msg) {
  if (!msg.sameUrl) {
    return;
  }
  clear(false);
});

// Hello is sent from every newly connected user, this way they will receive what has already been drawn:
TogetherJS.hub.on('togetherjs.hello', function () {
  TogetherJS.send({
    type: 'init',
    lines: lines
  });
});

// Draw initially received drawings:
TogetherJS.hub.on('init', function (msg) {
  reDraw(msg.lines);
  lines = msg.lines;
});

// JQuery to handle buttons and resizing events, also changes the cursor to a dot resembling the brush size:
$(document).ready(function () {
  // changeMouse creates a temporary invisible canvas that shows the cursor, which is then set as the cursor through css:
  function changeMouse() {
    // Makes sure the cursorSize is scaled:
    var cursorSize = context.lineWidth*(canvas.width/1140);
    if (cursorSize < 10){
        cursorSize = 10;
    }
    var cursorColor = context.strokeStyle;
    var cursorGenerator = document.createElement('canvas');
    cursorGenerator.width = cursorSize;
    cursorGenerator.height = cursorSize;
    var ctx = cursorGenerator.getContext('2d');

    var centerX = cursorGenerator.width/2;
    var centerY = cursorGenerator.height/2;

    ctx.beginPath();
    ctx.arc(centerX, centerY, (cursorSize/2)-4, 0, 2 * Math.PI, false);
    ctx.lineWidth = 3;
    ctx.strokeStyle = cursorColor;
    ctx.stroke();
    $('#sketch').css('cursor', 'url(' + cursorGenerator.toDataURL('image/png') + ') ' + cursorSize/2 + ' ' + cursorSize/2 + ',crosshair');
  }
  // Init mouse
  changeMouse();

  // Redraws the lines whenever the canvas is resized:
  $(window).resize(function() {
    if ($('#sketchContainer').width() != oWidth) {
      canvas.width = $('#sketchContainer').width();
      canvas.height = (canvas.width/1140)*400;
      $('#sketchContainer').outerHeight(String(canvas.height)+"px", true);
      var ratio = canvas.width/oWidth;
      oWidth = canvas.width;
      oHeight = canvas.height;
      reDraw(lines);
      changeMouse();
    }
  });

  // Clears the canvas:
  $('.clear').click(function () {
    clear(true);
  });

  // Color-button functions:
  $('.color-picker').click(function () {
    var $this = $(this);
    console.log($this);
    setColor($this.css("background-color"));
    changeMouse();
  });

  $('.eraser').click(function () {
    eraser();
    changeMouse();
  });
  // TogetherJS user color:
  $('.user-color-pick').click(function() {
    setColor(TogetherJS.require('peers').Self.color);
    changeMouse();
  });

  // Increase/decrease brush size:
  $('.plus-size').click(function() {
    setSize(context.lineWidth+3);
    changeMouse();
  });

  $('.minus-size').click(function() {
    if (context.lineWidth > 3) {
      setSize(context.lineWidth-3);
    }
    changeMouse();
  });
});
<|endoftext|>


<commit_before>var Discord = require('discord.io');
var request = require('request');

// Define the bot instance
var bot = new Discord.Client({
    autorun: true,
    token: ""
});

// Reconnect the bot if they somehow get disconnected
bot.on('disconnect', function(errMsg, code) {
  bot.connect()
});

// Make sure the emojis are fresh from the repo
function getEmojiFonts(){
  request('https://github.com/haykam821/Discord-Font-Service/raw/master/index.json', function (error, response, body) {
    if (!error && response.statusCode == 200) {
       importedJSON = JSON.parse(body);
       fonts = importedJSON;
    }
  })
};

setInterval(getEmojiFonts,3000);

// Define fonts in case it hasn't loaded yet
var fonts = {};

// Define a function to convert from text to letter emoji
function parseToEmoji(cmd, message) {
  try {
    array = message.toLowerCase().replace(cmd + ' ', '').split(' ');
    font = fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]] == undefined ? fonts['default'] : fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]]
    // Font setup
    if (array[array.length - 1].startsWith('font:')) {
        array.pop();
    } else {
        font = fonts['default'];
    };
    // Fix array again
    array = array.join(' ').split('');
    // Convert to emoji!
    for (var i = 0; i < array.length; i++) {
        console.log(font[1][array[i]])
        array[i] = font[1][array[i]] == undefined ? array[i] : font[1][array[i]]
    }
    return array.join('');
  } catch (err) {
    return `Fail. ${err}`;
  }
}

// Define a function that allows checking commands easily
function cmd(message, cmdname) {
  return message.toLowerCase().startsWith(cmdname);
}

// Message handling
bot.on('message', function(user, userID, channelID, message, event) {
  try {
    if (userID == bot.id) {
        // Respond to commands using parse to emoji function
        if (message.toLowerCase().startsWith('>e ')) {
            bot.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {description:parseToEmoji('>e', message)},
            });
        }
        // Respond to commands using parse to emoji function, but output as raw
        if (message.toLowerCase().startsWith('>er ')) {
            bot.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {description:`\`\`\`\n${parseToEmoji('>er',message)}\`\`\``},
            });
        }
    }
  } catch (err) {
    console.log('Error (bot 1): ' + err);
  }
});
<commit_msg>Generalized logged error message<commit_after>var Discord = require('discord.io');
var request = require('request');

// Define the bot instance
var bot = new Discord.Client({
    autorun: true,
    token: ""
});

// Reconnect the bot if they somehow get disconnected
bot.on('disconnect', function(errMsg, code) {
  bot.connect()
});

// Make sure the emojis are fresh from the repo
function getEmojiFonts(){
  request('https://github.com/haykam821/Discord-Font-Service/raw/master/index.json', function (error, response, body) {
    if (!error && response.statusCode == 200) {
       importedJSON = JSON.parse(body);
       fonts = importedJSON;
    }
  })
};

setInterval(getEmojiFonts,3000);

// Define fonts in case it hasn't loaded yet
var fonts = {};

// Define a function to convert from text to letter emoji
function parseToEmoji(cmd, message) {
  try {
    array = message.toLowerCase().replace(cmd + ' ', '').split(' ');
    font = fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]] == undefined ? fonts['default'] : fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]]
    // Font setup
    if (array[array.length - 1].startsWith('font:')) {
        array.pop();
    } else {
        font = fonts['default'];
    };
    // Fix array again
    array = array.join(' ').split('');
    // Convert to emoji!
    for (var i = 0; i < array.length; i++) {
        console.log(font[1][array[i]])
        array[i] = font[1][array[i]] == undefined ? array[i] : font[1][array[i]]
    }
    return array.join('');
  } catch (err) {
    return `Fail. ${err}`;
  }
}

// Define a function that allows checking commands easily
function cmd(message, cmdname) {
  return message.toLowerCase().startsWith(cmdname);
}

// Message handling
bot.on('message', function(user, userID, channelID, message, event) {
  try {
    if (userID == bot.id) {
        // Respond to commands using parse to emoji function
        if (message.toLowerCase().startsWith('>e ')) {
            bot.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {description:parseToEmoji('>e', message)},
            });
        }
        // Respond to commands using parse to emoji function, but output as raw
        if (message.toLowerCase().startsWith('>er ')) {
            bot.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {description:`\`\`\`\n${parseToEmoji('>er',message)}\`\`\``},
            });
        }
    }
  } catch (err) {
    console.log('Error: ' + err);
  }
});
<|endoftext|>


<commit_before>var Discord = require('discord.io');
var request = require('request');

// Define the bot instance
var bot = new Discord.Client({
    autorun: true,
    token: ""
});

// Reconnect the bot if they somehow get disconnected
bot.on('disconnect', function(errMsg, code) {
  bot.connect()
});

// Make sure the emojis are fresh from the repo
function getEmojiFonts(){
  request('https://github.com/haykam821/Discord-Font-Service/raw/master/index.json', function (error, response, body) {
    if (!error && response.statusCode == 200) {
       importedJSON = JSON.parse(body);
       fonts = importedJSON['fonts'];;
    }
  })
};

setInterval(getEmojiFonts,3000);

// Define fonts in case it hasn't loaded yet
var fonts = {};

// Define a function to convert from text to letter emoji
function parseToEmoji(cmd, message) {
  try {
    array = message.toLowerCase().replace(cmd + ' ', '').split(' ');
    font = fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]] == undefined ? fonts['default'] : fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]]
    // Font setup
    if (array[array.length - 1].startsWith('font:')) {
        array.pop();
    } else {
        font = fonts['default'];
    };
    // Fix array again
    array = array.join(' ').split('');
    // Convert to emoji!
    for (var i = 0; i < array.length; i++) {
        console.log(font['emojis'][array[i]])
        array[i] = font['emojis'][array[i]] == undefined ? array[i] : font['emojis'][array[i]]
    }
    return {
      font: font == fonts['default'] ? `Default (${fonts.default['font_name']})` : font['font_name'],
      text: array.join(''),
      color: parseInt(font['color'])
    }
  } catch (err) {
    return {
      font: 'None',
      text: `Fail. ${err}`,
      color: 0xFF1100
    }
  }
}

// Define a function that allows checking commands easily
function cmd(message, cmdname) {
  return message.toLowerCase().startsWith(cmdname);
}

// Message handling
bot.on('message', function(user, userID, channelID, message, event) {
  try {
    if (userID == bot.id) {
        // Respond to commands using parse to emoji function
        if (message.toLowerCase().startsWith('>fontify ')) {
            var result = parseToEmoji('>fontify',message);
            bot1.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {color:result.color,description:result.text,footer:{text:`Font: ${result.font}`}},
            });
        }
        // Respond to commands using parse to emoji function, but output as raw
        if (message.toLowerCase().startsWith('>codeify')) {
            var result = parseToEmoji('>codeify',message);
            bot1.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {color:result.color,description:`\`\`\`\n${result.text}\`\`\``,footer:{text:`Font: ${result.font}`}},
            });
        }
    }
  } catch (err) {
    console.log('Error: ' + err);
  }
});
<commit_msg>Fixed a double semicolon<commit_after>var Discord = require('discord.io');
var request = require('request');

// Define the bot instance
var bot = new Discord.Client({
    autorun: true,
    token: ""
});

// Reconnect the bot if they somehow get disconnected
bot.on('disconnect', function(errMsg, code) {
  bot.connect()
});

// Make sure the emojis are fresh from the repo
function getEmojiFonts(){
  request('https://github.com/haykam821/Discord-Font-Service/raw/master/index.json', function (error, response, body) {
    if (!error && response.statusCode == 200) {
       importedJSON = JSON.parse(body);
       fonts = importedJSON['fonts'];
    }
  })
};

setInterval(getEmojiFonts,3000);

// Define fonts in case it hasn't loaded yet
var fonts = {};

// Define a function to convert from text to letter emoji
function parseToEmoji(cmd, message) {
  try {
    array = message.toLowerCase().replace(cmd + ' ', '').split(' ');
    font = fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]] == undefined ? fonts['default'] : fonts[message.replace('font:', '').split(' ')[message.split(' ').length - 1]]
    // Font setup
    if (array[array.length - 1].startsWith('font:')) {
        array.pop();
    } else {
        font = fonts['default'];
    };
    // Fix array again
    array = array.join(' ').split('');
    // Convert to emoji!
    for (var i = 0; i < array.length; i++) {
        console.log(font['emojis'][array[i]])
        array[i] = font['emojis'][array[i]] == undefined ? array[i] : font['emojis'][array[i]]
    }
    return {
      font: font == fonts['default'] ? `Default (${fonts.default['font_name']})` : font['font_name'],
      text: array.join(''),
      color: parseInt(font['color'])
    }
  } catch (err) {
    return {
      font: 'None',
      text: `Fail. ${err}`,
      color: 0xFF1100
    }
  }
}

// Define a function that allows checking commands easily
function cmd(message, cmdname) {
  return message.toLowerCase().startsWith(cmdname);
}

// Message handling
bot.on('message', function(user, userID, channelID, message, event) {
  try {
    if (userID == bot.id) {
        // Respond to commands using parse to emoji function
        if (message.toLowerCase().startsWith('>fontify ')) {
            var result = parseToEmoji('>fontify',message);
            bot1.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {color:result.color,description:result.text,footer:{text:`Font: ${result.font}`}},
            });
        }
        // Respond to commands using parse to emoji function, but output as raw
        if (message.toLowerCase().startsWith('>codeify')) {
            var result = parseToEmoji('>codeify',message);
            bot1.editMessage({
                channelID: channelID,
                messageID: event.d.id,
                message: message,
                embed: {color:result.color,description:`\`\`\`\n${result.text}\`\`\``,footer:{text:`Font: ${result.font}`}},
            });
        }
    }
  } catch (err) {
    console.log('Error: ' + err);
  }
});
<|endoftext|>